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.BufferedReader; 041import java.io.File; 042import java.io.FileReader; 043import java.io.IOException; 044import java.io.OutputStream; 045import java.io.PrintStream; 046import java.util.ArrayList; 047import java.util.Arrays; 048import java.util.Collections; 049import java.util.Iterator; 050import java.util.LinkedHashMap; 051import java.util.List; 052import java.util.concurrent.atomic.AtomicReference; 053 054import com.unboundid.ldap.sdk.CompareRequest; 055import com.unboundid.ldap.sdk.Control; 056import com.unboundid.ldap.sdk.DN; 057import com.unboundid.ldap.sdk.ExtendedResult; 058import com.unboundid.ldap.sdk.LDAPConnection; 059import com.unboundid.ldap.sdk.LDAPConnectionOptions; 060import com.unboundid.ldap.sdk.LDAPConnectionPool; 061import com.unboundid.ldap.sdk.LDAPException; 062import com.unboundid.ldap.sdk.LDAPResult; 063import com.unboundid.ldap.sdk.ResultCode; 064import com.unboundid.ldap.sdk.UnsolicitedNotificationHandler; 065import com.unboundid.ldap.sdk.Version; 066import com.unboundid.ldap.sdk.controls.AssertionRequestControl; 067import com.unboundid.ldap.sdk.controls.AuthorizationIdentityRequestControl; 068import com.unboundid.ldap.sdk.controls.ManageDsaITRequestControl; 069import com.unboundid.ldap.sdk.controls.ProxiedAuthorizationV1RequestControl; 070import com.unboundid.ldap.sdk.controls.ProxiedAuthorizationV2RequestControl; 071import com.unboundid.ldap.sdk.unboundidds.controls. 072 GetAuthorizationEntryRequestControl; 073import com.unboundid.ldap.sdk.unboundidds.controls. 074 GetUserResourceLimitsRequestControl; 075import com.unboundid.ldap.sdk.unboundidds.controls. 076 OperationPurposeRequestControl; 077import com.unboundid.ldap.sdk.unboundidds.controls. 078 PasswordPolicyRequestControl; 079import com.unboundid.ldap.sdk.unboundidds.extensions. 080 StartAdministrativeSessionExtendedRequest; 081import com.unboundid.ldap.sdk.unboundidds.extensions. 082 StartAdministrativeSessionPostConnectProcessor; 083import com.unboundid.util.Base64; 084import com.unboundid.util.DNFileReader; 085import com.unboundid.util.Debug; 086import com.unboundid.util.LDAPCommandLineTool; 087import com.unboundid.util.NotNull; 088import com.unboundid.util.Nullable; 089import com.unboundid.util.ObjectPair; 090import com.unboundid.util.StaticUtils; 091import com.unboundid.util.ThreadSafety; 092import com.unboundid.util.ThreadSafetyLevel; 093import com.unboundid.util.args.ArgumentException; 094import com.unboundid.util.args.ArgumentParser; 095import com.unboundid.util.args.BooleanArgument; 096import com.unboundid.util.args.ControlArgument; 097import com.unboundid.util.args.DNArgument; 098import com.unboundid.util.args.FileArgument; 099import com.unboundid.util.args.FilterArgument; 100import com.unboundid.util.args.StringArgument; 101 102import static com.unboundid.ldap.sdk.unboundidds.tools.ToolMessages.*; 103 104 105 106/** 107 * This class provide an LDAP command-line tool that may be used to perform 108 * compare operations in an LDAP directory server. 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 */ 120@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 121public final class LDAPCompare 122 extends LDAPCommandLineTool 123 implements UnsolicitedNotificationHandler 124{ 125 /** 126 * The column at which output should be wrapped. 127 */ 128 private static final int WRAP_COLUMN = StaticUtils.TERMINAL_WIDTH_COLUMNS - 1; 129 130 131 132 /** 133 * The value used to select the CSV output format. 134 */ 135 @NotNull private static final String OUTPUT_FORMAT_CSV = "csv"; 136 137 138 139 /** 140 * The value used to select the JSON output format. 141 */ 142 @NotNull private static final String OUTPUT_FORMAT_JSON = "json"; 143 144 145 146 /** 147 * The value used to select the tab-delimited output format. 148 */ 149 @NotNull private static final String OUTPUT_FORMAT_TAB_DELIMITED = 150 "tab-delimited"; 151 152 153 154 // A reference to the completion message to return for this tool. 155 @NotNull private final AtomicReference<String> completionMessage; 156 157 // A reference to the argument parser for this tool. 158 @Nullable private ArgumentParser argumentParser; 159 160 // The supported command-line arguments. 161 @Nullable private BooleanArgument authorizationIdentity; 162 @Nullable private BooleanArgument continueOnError; 163 @Nullable private BooleanArgument dryRun; 164 @Nullable private BooleanArgument followReferrals; 165 @Nullable private BooleanArgument getUserResourceLimits; 166 @Nullable private BooleanArgument manageDsaIT; 167 @Nullable private BooleanArgument scriptFriendly; 168 @Nullable private BooleanArgument teeOutput; 169 @Nullable private BooleanArgument terse; 170 @Nullable private BooleanArgument useAdministrativeSession; 171 @Nullable private BooleanArgument useCompareResultCodeAsExitCode; 172 @Nullable private BooleanArgument usePasswordPolicyControl; 173 @Nullable private BooleanArgument verbose; 174 @Nullable private ControlArgument bindControl; 175 @Nullable private ControlArgument compareControl; 176 @Nullable private DNArgument proxyV1As; 177 @Nullable private FileArgument assertionFile; 178 @Nullable private FileArgument dnFile; 179 @Nullable private FileArgument outputFile; 180 @Nullable private FilterArgument assertionFilter; 181 @Nullable private StringArgument getAuthorizationEntryAttribute; 182 @Nullable private StringArgument operationPurpose; 183 @Nullable private StringArgument outputFormat; 184 @Nullable private StringArgument proxyAs; 185 186 187 188 /** 189 * Invokes this tool with the provided set of arguments. The default standard 190 * output and error streams will be used. 191 * 192 * @param args The command-line arguments provided to this program. 193 */ 194 public static void main(@NotNull final String... args) 195 { 196 final ResultCode resultCode = main(System.out, System.err, args); 197 if (resultCode != ResultCode.SUCCESS) 198 { 199 System.exit(resultCode.intValue()); 200 } 201 } 202 203 204 205 /** 206 * Invokes this tool with the provided set of arguments, and using the 207 * provided streams for standard output and error. 208 * 209 * @param out The output stream to use for standard output. It may be 210 * {@code null} if standard output should be suppressed. 211 * @param err The output stream to use for standard error. It may be 212 * {@code null} if standard error should be suppressed. 213 * @param args The command-line arguments provided to this program. 214 * 215 * @return The result code obtained when running the tool. Any result code 216 * other than {@link ResultCode#SUCCESS} indicates an error. 217 */ 218 @NotNull() 219 public static ResultCode main(@Nullable final OutputStream out, 220 @Nullable final OutputStream err, 221 @NotNull final String... args) 222 { 223 final LDAPCompare tool = new LDAPCompare(out, err); 224 return tool.runTool(args); 225 } 226 227 228 229 /** 230 * Creates a new instance of this tool with the provided output and error 231 * streams. 232 * 233 * @param out The output stream to use for standard output. It may be 234 * {@code null} if standard output should be suppressed. 235 * @param err The output stream to use for standard error. It may be 236 * {@code null} if standard error should be suppressed. 237 */ 238 public LDAPCompare(@Nullable final OutputStream out, 239 @Nullable final OutputStream err) 240 { 241 super(out, err); 242 243 completionMessage = new AtomicReference<>(); 244 245 argumentParser = null; 246 247 authorizationIdentity = null; 248 continueOnError = null; 249 dryRun = null; 250 followReferrals = null; 251 getUserResourceLimits = null; 252 manageDsaIT = null; 253 scriptFriendly = null; 254 teeOutput = null; 255 terse = null; 256 useAdministrativeSession = null; 257 useCompareResultCodeAsExitCode = null; 258 usePasswordPolicyControl = null; 259 verbose = null; 260 bindControl = null; 261 compareControl = null; 262 proxyV1As = null; 263 assertionFile = null; 264 dnFile = null; 265 outputFile = null; 266 assertionFilter = null; 267 getAuthorizationEntryAttribute = null; 268 operationPurpose = null; 269 outputFormat = null; 270 proxyAs = null; 271 } 272 273 274 275 /** 276 * {@inheritDoc} 277 */ 278 @Override() 279 @NotNull() 280 public String getToolName() 281 { 282 return "ldapcompare"; 283 } 284 285 286 287 /** 288 * {@inheritDoc} 289 */ 290 @Override() 291 @NotNull() 292 public String getToolDescription() 293 { 294 return INFO_LDAPCOMPARE_TOOL_DESCRIPTION_1.get(); 295 } 296 297 298 299 /** 300 * {@inheritDoc} 301 */ 302 @Override() 303 @NotNull() 304 public List<String> getAdditionalDescriptionParagraphs() 305 { 306 return Collections.unmodifiableList(Arrays.asList( 307 INFO_LDAPCOMPARE_TOOL_DESCRIPTION_2.get(), 308 INFO_LDAPCOMPARE_TOOL_DESCRIPTION_3.get(), 309 INFO_LDAPCOMPARE_TOOL_DESCRIPTION_4.get(), 310 INFO_LDAPCOMPARE_TOOL_DESCRIPTION_5.get())); 311 } 312 313 314 315 /** 316 * {@inheritDoc} 317 */ 318 @Override() 319 @NotNull() 320 public String getToolVersion() 321 { 322 return Version.NUMERIC_VERSION_STRING; 323 } 324 325 326 327 /** 328 * {@inheritDoc} 329 */ 330 @Override() 331 public int getMinTrailingArguments() 332 { 333 return 0; 334 } 335 336 337 338 /** 339 * {@inheritDoc} 340 */ 341 @Override() 342 public int getMaxTrailingArguments() 343 { 344 return -1; 345 } 346 347 348 349 /** 350 * {@inheritDoc} 351 */ 352 @Override() 353 @NotNull() 354 public String getTrailingArgumentsPlaceholder() 355 { 356 return INFO_LDAPCOMPARE_TRAILING_ARGS_PLACEHOLDER.get(); 357 } 358 359 360 361 /** 362 * {@inheritDoc} 363 */ 364 @Override() 365 public boolean supportsInteractiveMode() 366 { 367 return true; 368 } 369 370 371 372 /** 373 * {@inheritDoc} 374 */ 375 @Override() 376 public boolean defaultsToInteractiveMode() 377 { 378 return true; 379 } 380 381 382 383 /** 384 * {@inheritDoc} 385 */ 386 @Override() 387 public boolean supportsPropertiesFile() 388 { 389 return true; 390 } 391 392 393 394 /** 395 * {@inheritDoc} 396 */ 397 @Override() 398 protected boolean supportsDebugLogging() 399 { 400 return true; 401 } 402 403 404 405 /** 406 * {@inheritDoc} 407 */ 408 @Override() 409 protected boolean supportsAuthentication() 410 { 411 return true; 412 } 413 414 415 416 /** 417 * {@inheritDoc} 418 */ 419 @Override() 420 protected boolean defaultToPromptForBindPassword() 421 { 422 return true; 423 } 424 425 426 427 /** 428 * {@inheritDoc} 429 */ 430 @Override() 431 protected boolean supportsSASLHelp() 432 { 433 return true; 434 } 435 436 437 438 /** 439 * {@inheritDoc} 440 */ 441 @Override() 442 protected boolean includeAlternateLongIdentifiers() 443 { 444 return true; 445 } 446 447 448 449 /** 450 * {@inheritDoc} 451 */ 452 @Override() 453 @NotNull() 454 protected List<Control> getBindControls() 455 { 456 final List<Control> bindControls = new ArrayList<>(10); 457 458 if (bindControl.isPresent()) 459 { 460 bindControls.addAll(bindControl.getValues()); 461 } 462 463 if (authorizationIdentity.isPresent()) 464 { 465 bindControls.add(new AuthorizationIdentityRequestControl(false)); 466 } 467 468 if (getAuthorizationEntryAttribute.isPresent()) 469 { 470 bindControls.add(new GetAuthorizationEntryRequestControl(true, true, 471 getAuthorizationEntryAttribute.getValues())); 472 } 473 474 if (getUserResourceLimits.isPresent()) 475 { 476 bindControls.add(new GetUserResourceLimitsRequestControl()); 477 } 478 479 if (usePasswordPolicyControl.isPresent()) 480 { 481 bindControls.add(new PasswordPolicyRequestControl()); 482 } 483 484 return bindControls; 485 } 486 487 488 489 /** 490 * {@inheritDoc} 491 */ 492 @Override() 493 protected boolean supportsMultipleServers() 494 { 495 return true; 496 } 497 498 499 500 /** 501 * {@inheritDoc} 502 */ 503 @Override() 504 protected boolean supportsSSLDebugging() 505 { 506 return true; 507 } 508 509 510 511 /** 512 * {@inheritDoc} 513 */ 514 @Override() 515 @NotNull() 516 public LDAPConnectionOptions getConnectionOptions() 517 { 518 final LDAPConnectionOptions options = new LDAPConnectionOptions(); 519 520 options.setUseSynchronousMode(true); 521 options.setFollowReferrals(followReferrals.isPresent()); 522 options.setUnsolicitedNotificationHandler(this); 523 options.setResponseTimeoutMillis(0L); 524 525 return options; 526 } 527 528 529 530 /** 531 * {@inheritDoc} 532 */ 533 @Override() 534 protected boolean logToolInvocationByDefault() 535 { 536 return false; 537 } 538 539 540 541 /** 542 * {@inheritDoc} 543 */ 544 @Override() 545 @Nullable() 546 protected String getToolCompletionMessage() 547 { 548 return completionMessage.get(); 549 } 550 551 552 553 /** 554 * {@inheritDoc} 555 */ 556 @Override() 557 public void addNonLDAPArguments(@NotNull final ArgumentParser parser) 558 throws ArgumentException 559 { 560 argumentParser = parser; 561 562 // Compare operation processing arguments. 563 dnFile = new FileArgument('f', "dnFile", false, 1, null, 564 INFO_LDAPCOMPARE_ARG_DESCRIPTION_DN_FILE.get(), true, true, true, 565 false); 566 dnFile.addLongIdentifier("dn-file", true); 567 dnFile.addLongIdentifier("filename", true); 568 dnFile.setArgumentGroupName(INFO_LDAPCOMPARE_ARG_GROUP_PROCESSING.get()); 569 parser.addArgument(dnFile); 570 571 assertionFile = new FileArgument(null, "assertionFile", false, 1, null, 572 INFO_LDAPCOMPARE_ARG_DESCRIPTION_ASSERTION_FILE.get(), true, true, 573 true, false); 574 assertionFile.addLongIdentifier("assertion-file", true); 575 assertionFile.setArgumentGroupName( 576 INFO_LDAPCOMPARE_ARG_GROUP_PROCESSING.get()); 577 parser.addArgument(assertionFile); 578 579 followReferrals = new BooleanArgument(null, "followReferrals", 1, 580 INFO_LDAPCOMPARE_ARG_DESCRIPTION_FOLLOW_REFERRALS.get()); 581 followReferrals.addLongIdentifier("follow-referrals", true); 582 followReferrals.setArgumentGroupName( 583 INFO_LDAPCOMPARE_ARG_GROUP_PROCESSING.get()); 584 parser.addArgument(followReferrals); 585 586 useAdministrativeSession = new BooleanArgument(null, 587 "useAdministrativeSession", 1, 588 INFO_LDAPCOMPARE_ARG_DESCRIPTION_USE_ADMIN_SESSION.get()); 589 useAdministrativeSession.addLongIdentifier("use-administrative-session", 590 true); 591 useAdministrativeSession.setArgumentGroupName( 592 INFO_LDAPCOMPARE_ARG_GROUP_PROCESSING.get()); 593 parser.addArgument(useAdministrativeSession); 594 595 continueOnError = new BooleanArgument('c', "continueOnError", 1, 596 INFO_LDAPCOMPARE_ARG_DESCRIPTION_CONTINUE_ON_ERROR.get()); 597 continueOnError.addLongIdentifier("continue-on-error", true); 598 continueOnError.setArgumentGroupName( 599 INFO_LDAPCOMPARE_ARG_GROUP_PROCESSING.get()); 600 parser.addArgument(continueOnError); 601 602 dryRun = new BooleanArgument('n', "dryRun", 1, 603 INFO_LDAPCOMPARE_ARG_DESCRIPTION_DRY_RUN.get()); 604 dryRun.addLongIdentifier("dry-run", true); 605 dryRun.setArgumentGroupName(INFO_LDAPCOMPARE_ARG_GROUP_PROCESSING.get()); 606 parser.addArgument(dryRun); 607 608 609 // Bind control arguments. 610 bindControl = new ControlArgument(null, "bindControl", false, 0, null, 611 INFO_LDAPCOMPARE_ARG_DESCRIPTION_BIND_CONTROL.get()); 612 bindControl.addLongIdentifier("bind-control", true); 613 bindControl.setArgumentGroupName( 614 INFO_LDAPCOMPARE_ARG_GROUP_BIND_CONTROLS.get()); 615 parser.addArgument(bindControl); 616 617 authorizationIdentity = new BooleanArgument('E', "authorizationIdentity", 1, 618 INFO_LDAPCOMPARE_ARG_DESCRIPTION_AUTHZ_IDENTITY.get()); 619 authorizationIdentity.addLongIdentifier("authorization-identity", true); 620 authorizationIdentity.addLongIdentifier("useAuthorizationIdentity", true); 621 authorizationIdentity.addLongIdentifier("use-authorization-identity", true); 622 authorizationIdentity.addLongIdentifier("useAuthorizationIdentityControl", 623 true); 624 authorizationIdentity.addLongIdentifier( 625 "use-authorization-identity-control", true); 626 authorizationIdentity.setArgumentGroupName( 627 INFO_LDAPCOMPARE_ARG_GROUP_BIND_CONTROLS.get()); 628 parser.addArgument(authorizationIdentity); 629 630 usePasswordPolicyControl = new BooleanArgument(null, 631 "usePasswordPolicyControl", 1, 632 INFO_LDAPCOMPARE_ARG_DESCRIPTION_USE_PW_POLICY_CONTROL.get()); 633 usePasswordPolicyControl.addLongIdentifier("use-password-policy-control", 634 true); 635 usePasswordPolicyControl.addLongIdentifier("passwordPolicyControl", true); 636 usePasswordPolicyControl.addLongIdentifier("password-policy-control", true); 637 usePasswordPolicyControl.addLongIdentifier("passwordPolicy", true); 638 usePasswordPolicyControl.addLongIdentifier("password-policy", true); 639 usePasswordPolicyControl.setArgumentGroupName( 640 INFO_LDAPCOMPARE_ARG_GROUP_BIND_CONTROLS.get()); 641 parser.addArgument(usePasswordPolicyControl); 642 643 getAuthorizationEntryAttribute = new StringArgument(null, 644 "getAuthorizationEntryAttribute", false, 0, 645 INFO_LDAPCOMPARE_ARG_PLACEHOLDER_ATTRIBUTE.get(), 646 INFO_LDAPCOMPARE_ARG_DESCRIPTION_GET_AUTHZ_ENTRY_ATTR.get()); 647 getAuthorizationEntryAttribute.addLongIdentifier( 648 "get-authorization-entry-attribute", true); 649 getAuthorizationEntryAttribute.addLongIdentifier("getAuthzEntryAttribute", 650 true); 651 getAuthorizationEntryAttribute.addLongIdentifier( 652 "get-authz-entry-attribute", true); 653 getAuthorizationEntryAttribute.setArgumentGroupName( 654 INFO_LDAPCOMPARE_ARG_GROUP_BIND_CONTROLS.get()); 655 parser.addArgument(getAuthorizationEntryAttribute); 656 657 getUserResourceLimits = new BooleanArgument(null, "getUserResourceLimits", 658 1, INFO_LDAPCOMPARE_ARG_PLACEHOLDER_GET_USER_RESOURCE_LIMITS.get()); 659 getUserResourceLimits.addLongIdentifier("get-user-resource-limits", true); 660 getUserResourceLimits.setArgumentGroupName( 661 INFO_LDAPCOMPARE_ARG_GROUP_BIND_CONTROLS.get()); 662 parser.addArgument(getUserResourceLimits); 663 664 665 // Compare control arguments. 666 compareControl = new ControlArgument('J', "compareControl", false, 0, null, 667 INFO_LDAPCOMPARE_ARG_DESCRIPTION_COMPARE_CONTROL.get()); 668 compareControl.addLongIdentifier("compare-control", true); 669 compareControl.addLongIdentifier("control", true); 670 compareControl.setArgumentGroupName( 671 INFO_LDAPCOMPARE_ARG_GROUP_COMPARE_CONTROLS.get()); 672 parser.addArgument(compareControl); 673 674 proxyAs = new StringArgument('Y', "proxyAs", false, 1, 675 INFO_LDAPCOMPARE_ARG_PLACEHOLDER_AUTHZ_ID.get(), 676 INFO_LDAPCOMPARE_ARG_DESCRIPTION_PROXY_AS.get()); 677 proxyAs.addLongIdentifier("proxy-as", true); 678 proxyAs.addLongIdentifier("proxyV2As", true); 679 proxyAs.addLongIdentifier("proxy-v2-as", true); 680 proxyAs.addLongIdentifier("proxyV2", true); 681 proxyAs.addLongIdentifier("proxy-v2", true); 682 proxyAs.setArgumentGroupName( 683 INFO_LDAPCOMPARE_ARG_GROUP_COMPARE_CONTROLS.get()); 684 parser.addArgument(proxyAs); 685 686 proxyV1As = new DNArgument(null, "proxyV1As", false, 1, null, 687 INFO_LDAPCOMPARE_ARG_DESCRIPTION_PROXY_V1_AS.get()); 688 proxyV1As.addLongIdentifier("proxy-v1-as", true); 689 proxyV1As.addLongIdentifier("proxyV1", true); 690 proxyV1As.addLongIdentifier("proxy-v1", true); 691 proxyV1As.setArgumentGroupName( 692 INFO_LDAPCOMPARE_ARG_GROUP_COMPARE_CONTROLS.get()); 693 parser.addArgument(proxyV1As); 694 695 manageDsaIT = new BooleanArgument(null, "manageDsaIT", 1, 696 INFO_LDAPCOMPARE_ARG_DESCRIPTION_MANAGE_DSA_IT.get()); 697 manageDsaIT.addLongIdentifier("manage-dsa-it", true); 698 manageDsaIT.setArgumentGroupName( 699 INFO_LDAPCOMPARE_ARG_GROUP_COMPARE_CONTROLS.get()); 700 parser.addArgument(manageDsaIT); 701 702 assertionFilter = new FilterArgument(null, "assertionFilter", false, 1, 703 null, INFO_LDAPCOMPARE_ARG_DESCRIPTION_ASSERTION_FILTER.get()); 704 assertionFilter.addLongIdentifier("assertion-filter", true); 705 assertionFilter.addLongIdentifier("assertionControlFilter", true); 706 assertionFilter.addLongIdentifier("assertion-control-filter", true); 707 assertionFilter.addLongIdentifier("useAssertionControl", true); 708 assertionFilter.addLongIdentifier("use-assertion-control", true); 709 assertionFilter.setArgumentGroupName( 710 INFO_LDAPCOMPARE_ARG_GROUP_COMPARE_CONTROLS.get()); 711 parser.addArgument(assertionFilter); 712 713 operationPurpose = new StringArgument(null, "operationPurpose", false, 1, 714 INFO_LDAPCOMPARE_ARG_PLACEHOLDER_PURPOSE.get(), 715 INFO_LDAPCOMPARE_ARG_DESCRIPTION_OPERATION_PURPOSE.get()); 716 operationPurpose.addLongIdentifier("operation-purpose", true); 717 operationPurpose.addLongIdentifier("purpose", true); 718 operationPurpose.setArgumentGroupName( 719 INFO_LDAPCOMPARE_ARG_GROUP_COMPARE_CONTROLS.get()); 720 parser.addArgument(operationPurpose); 721 722 723 // Output Arguments. 724 outputFile = new FileArgument(null, "outputFile", false, 1, null, 725 INFO_LDAPCOMPARE_ARG_DESCRIPTION_OUTPUT_FILE.get(), false, true, true, 726 false); 727 outputFile.addLongIdentifier("output-file", true); 728 outputFile.setArgumentGroupName(INFO_LDAPCOMPARE_ARG_GROUP_OUTPUT.get()); 729 parser.addArgument(outputFile); 730 731 teeOutput = new BooleanArgument(null, "teeOutput", 1, 732 INFO_LDAPCOMPARE_ARG_DESCRIPTION_TEE_OUTPUT.get()); 733 teeOutput.addLongIdentifier("tee-output", true); 734 teeOutput.addLongIdentifier("tee", true); 735 teeOutput.setArgumentGroupName(INFO_LDAPCOMPARE_ARG_GROUP_OUTPUT.get()); 736 parser.addArgument(teeOutput); 737 738 outputFormat = new StringArgument(null, "outputFormat", false, 1, 739 INFO_LDAPCOMPARE_ARG_PLACEHOLDER_FORMAT.get(), 740 INFO_LDAPCOMPARE_ARG_DESCRIPTION_OUTPUT_FORMAT.get(), 741 StaticUtils.setOf( 742 OUTPUT_FORMAT_TAB_DELIMITED, 743 OUTPUT_FORMAT_CSV, 744 OUTPUT_FORMAT_JSON), 745 OUTPUT_FORMAT_TAB_DELIMITED); 746 outputFormat.addLongIdentifier("output-format", true); 747 outputFormat.setArgumentGroupName(INFO_LDAPCOMPARE_ARG_GROUP_OUTPUT.get()); 748 parser.addArgument(outputFormat); 749 750 scriptFriendly = new BooleanArgument(null, "scriptFriendly", 1, 751 INFO_LDAPCOMPARE_ARG_DESCRIPTION_SCRIPT_FRIENDLY.get()); 752 scriptFriendly.addLongIdentifier("script-friendly", true); 753 scriptFriendly.setArgumentGroupName( 754 INFO_LDAPCOMPARE_ARG_GROUP_OUTPUT.get()); 755 scriptFriendly.setHidden(true); 756 parser.addArgument(scriptFriendly); 757 758 verbose = new BooleanArgument('v', "verbose", 1, 759 INFO_LDAPCOMPARE_ARG_DESCRIPTION_VERBOSE.get()); 760 verbose.setArgumentGroupName(INFO_LDAPCOMPARE_ARG_GROUP_OUTPUT.get()); 761 parser.addArgument(verbose); 762 763 terse = new BooleanArgument(null, "terse", 1, 764 INFO_LDAPCOMPARE_ARG_DESCRIPTION_TERSE.get()); 765 terse.setArgumentGroupName(INFO_LDAPCOMPARE_ARG_GROUP_OUTPUT.get()); 766 parser.addArgument(terse); 767 768 useCompareResultCodeAsExitCode = new BooleanArgument(null, 769 "useCompareResultCodeAsExitCode", 1, 770 INFO_LDAPCOMPARE_ARG_DESC_USE_COMPARE_RESULT_CODE_AS_EXIT_CODE.get()); 771 useCompareResultCodeAsExitCode.addLongIdentifier( 772 "use-compare-result-code-as-exit-code", true); 773 useCompareResultCodeAsExitCode.addLongIdentifier( 774 "useCompareResultCode", true); 775 useCompareResultCodeAsExitCode.addLongIdentifier( 776 "use-compare-result-code", true); 777 useCompareResultCodeAsExitCode.setArgumentGroupName( 778 INFO_LDAPCOMPARE_ARG_GROUP_OUTPUT.get()); 779 parser.addArgument(useCompareResultCodeAsExitCode); 780 781 parser.addExclusiveArgumentSet(dnFile, assertionFile); 782 783 parser.addExclusiveArgumentSet(proxyAs, proxyV1As); 784 785 parser.addDependentArgumentSet(teeOutput, outputFile); 786 787 parser.addExclusiveArgumentSet(verbose, terse); 788 } 789 790 791 792 /** 793 * {@inheritDoc} 794 */ 795 @Override() 796 @NotNull() 797 public ResultCode doToolProcessing() 798 { 799 final List<CompareRequest> compareRequests; 800 try 801 { 802 compareRequests = getCompareRequests(); 803 } 804 catch (final LDAPException e) 805 { 806 Debug.debugException(e); 807 logCompletionMessage(true, e.getMessage()); 808 return e.getResultCode(); 809 } 810 811 812 LDAPConnectionPool pool = null; 813 PrintStream writer = null; 814 try 815 { 816 // Create a connection pool that will be used to communicate with the 817 // directory server. If we should use an administrative session, then 818 // create a connect processor that will be used to start the session 819 // before performing the bind. 820 try 821 { 822 final StartAdministrativeSessionPostConnectProcessor p; 823 if (useAdministrativeSession.isPresent()) 824 { 825 p = new StartAdministrativeSessionPostConnectProcessor( 826 new StartAdministrativeSessionExtendedRequest(getToolName(), 827 true)); 828 } 829 else 830 { 831 p = null; 832 } 833 834 pool = getConnectionPool(1, 2, 0, p, null, true, 835 new ReportBindResultLDAPConnectionPoolHealthCheck(this, true, 836 verbose.isPresent())); 837 pool.setRetryFailedOperationsDueToInvalidConnections(true); 838 839 840 if (outputFile.isPresent()) 841 { 842 try 843 { 844 writer = new PrintStream(outputFile.getValue()); 845 } 846 catch (final Exception e) 847 { 848 Debug.debugException(e); 849 logCompletionMessage(true, 850 ERR_LDAPCOMPARE_CANNOT_OPEN_OUTPUT_FILE.get( 851 outputFile.getValue().getAbsolutePath(), 852 StaticUtils.getExceptionMessage(e))); 853 return ResultCode.LOCAL_ERROR; 854 } 855 } 856 else 857 { 858 writer = null; 859 } 860 861 862 final LDAPCompareOutputHandler outputHandler; 863 switch (StaticUtils.toLowerCase(outputFormat.getValue())) 864 { 865 case OUTPUT_FORMAT_CSV: 866 outputHandler = new LDAPCompareCSVOutputHandler(); 867 break; 868 case OUTPUT_FORMAT_JSON: 869 outputHandler = new LDAPCompareJSONOutputHandler(); 870 break; 871 case OUTPUT_FORMAT_TAB_DELIMITED: 872 default: 873 outputHandler = new LDAPCompareTabDelimitedOutputHandler(); 874 break; 875 } 876 877 if (! terse.isPresent()) 878 { 879 for (final String line : outputHandler.getHeaderLines()) 880 { 881 if (writer != null) 882 { 883 writer.println(line); 884 } 885 886 if ((writer == null) || teeOutput.isPresent()) 887 { 888 out(line); 889 } 890 } 891 } 892 893 894 ResultCode resultCode = null; 895 int numTrue = 0; 896 int numFalse = 0; 897 int numErrors = 0; 898 for (final CompareRequest compareRequest : compareRequests) 899 { 900 LDAPResult compareResult; 901 try 902 { 903 compareResult = pool.compare(compareRequest); 904 } 905 catch (final LDAPException e) 906 { 907 Debug.debugException(e); 908 compareResult = e.toLDAPResult(); 909 } 910 911 try 912 { 913 writeResult(writer, outputHandler, compareRequest, compareResult); 914 } 915 catch (final LDAPException e) 916 { 917 Debug.debugException(e); 918 logCompletionMessage(true, e.getMessage()); 919 return e.getResultCode(); 920 } 921 922 final ResultCode compareResultCode = compareResult.getResultCode(); 923 if (compareResultCode == ResultCode.COMPARE_TRUE) 924 { 925 numTrue++; 926 if (resultCode == null) 927 { 928 resultCode = ResultCode.COMPARE_TRUE; 929 } 930 } 931 else if (compareResultCode == ResultCode.COMPARE_FALSE) 932 { 933 numFalse++; 934 if (resultCode == null) 935 { 936 resultCode = ResultCode.COMPARE_FALSE; 937 } 938 } 939 else 940 { 941 numErrors++; 942 if ((resultCode == null) || 943 (resultCode == ResultCode.COMPARE_TRUE) || 944 (resultCode == ResultCode.COMPARE_FALSE)) 945 { 946 resultCode = compareResultCode; 947 } 948 949 if (! continueOnError.isPresent()) 950 { 951 return resultCode; 952 } 953 } 954 } 955 956 if (resultCode == ResultCode.COMPARE_TRUE) 957 { 958 if (compareRequests.size() > 1) 959 { 960 resultCode = ResultCode.SUCCESS; 961 logCompletionMessage(false, 962 INFO_LDAPCOMPARE_RESULT_ALL_SUCCEEDED.get(numTrue, numFalse)); 963 } 964 else 965 { 966 if (! useCompareResultCodeAsExitCode.isPresent()) 967 { 968 resultCode = ResultCode.SUCCESS; 969 } 970 971 logCompletionMessage(false, 972 INFO_LDAPCOMPARE_RESULT_COMPARE_MATCHED.get()); 973 } 974 } 975 else if (resultCode == ResultCode.COMPARE_FALSE) 976 { 977 if (compareRequests.size() > 1) 978 { 979 resultCode = ResultCode.SUCCESS; 980 logCompletionMessage(false, 981 INFO_LDAPCOMPARE_RESULT_ALL_SUCCEEDED.get(numTrue, numFalse)); 982 } 983 else 984 { 985 if (! useCompareResultCodeAsExitCode.isPresent()) 986 { 987 resultCode = ResultCode.SUCCESS; 988 } 989 990 logCompletionMessage(false, 991 INFO_LDAPCOMPARE_RESULT_COMPARE_DID_NOT_MATCH.get()); 992 } 993 } 994 else 995 { 996 if (compareRequests.size() > 1) 997 { 998 logCompletionMessage(true, 999 ERR_LDAPCOMPARE_RESULT_WITH_ERRORS.get(numErrors, numTrue, 1000 numFalse)); 1001 } 1002 else 1003 { 1004 logCompletionMessage(true, 1005 ERR_LDAPCOMPARE_RESULT_FAILED.get()); 1006 } 1007 } 1008 1009 return resultCode; 1010 } 1011 catch (final LDAPException le) 1012 { 1013 Debug.debugException(le); 1014 1015 // Unable to create the connection pool, which means that either the 1016 // connection could not be established or the attempt to authenticate 1017 // the connection failed. If the bind failed, then the report bind 1018 // result health check should have already reported the bind failure. 1019 // If the failure was something else, then display that failure result. 1020 if (le.getResultCode() != ResultCode.INVALID_CREDENTIALS) 1021 { 1022 for (final String line : 1023 ResultUtils.formatResult(le, true, 0, WRAP_COLUMN)) 1024 { 1025 err(line); 1026 } 1027 } 1028 return le.getResultCode(); 1029 } 1030 } 1031 finally 1032 { 1033 if (pool != null) 1034 { 1035 pool.close(); 1036 } 1037 1038 if (writer != null) 1039 { 1040 writer.close(); 1041 } 1042 } 1043 } 1044 1045 1046 1047 /** 1048 * Retrieves a list of the compare requests that should be issued. 1049 * 1050 * @return A list of the compare requests that should be issued. 1051 * 1052 * @throws LDAPException If a problem occurs while obtaining the compare 1053 * requests to process. 1054 */ 1055 @NotNull() 1056 private List<CompareRequest> getCompareRequests() 1057 throws LDAPException 1058 { 1059 final List<String> trailingArgs = argumentParser.getTrailingArguments(); 1060 final int numTrailingArgs = trailingArgs.size(); 1061 1062 if (assertionFile.isPresent()) 1063 { 1064 if (numTrailingArgs != 0) 1065 { 1066 throw new LDAPException(ResultCode.PARAM_ERROR, 1067 ERR_LDAPCOMPARE_TRAILING_ARGS_WITH_ASSERTION_FILE.get( 1068 assertionFile.getIdentifierString())); 1069 } 1070 1071 return readAssertionFile(getCompareControls()); 1072 } 1073 else if (dnFile.isPresent()) 1074 { 1075 if (numTrailingArgs != 1) 1076 { 1077 throw new LDAPException(ResultCode.PARAM_ERROR, 1078 ERR_LDAPCOMPARE_INVALID_TRAILING_ARG_COUNT_WITH_DN_FILE.get( 1079 dnFile.getIdentifierString())); 1080 } 1081 1082 final ObjectPair<String,byte[]> ava = 1083 parseAttributeValueAssertion(trailingArgs.get(0)); 1084 return readDNFile(ava.getFirst(), ava.getSecond(), getCompareControls()); 1085 } 1086 else 1087 { 1088 if (numTrailingArgs < 2) 1089 { 1090 throw new LDAPException(ResultCode.PARAM_ERROR, 1091 ERR_LDAPCOMPARE_INVALID_TRAILING_ARG_COUNT_WITHOUT_FILE.get( 1092 dnFile.getIdentifierString(), 1093 assertionFile.getIdentifierString())); 1094 } 1095 1096 final Iterator<String> trailingArgsIterator = trailingArgs.iterator(); 1097 final ObjectPair<String,byte[]> ava = 1098 parseAttributeValueAssertion(trailingArgsIterator.next()); 1099 final String attributeName = ava.getFirst(); 1100 final byte[] assertionValue = ava.getSecond(); 1101 1102 final Control[] controls = getCompareControls(); 1103 1104 final List<CompareRequest> requests = new ArrayList<>(numTrailingArgs-1); 1105 while (trailingArgsIterator.hasNext()) 1106 { 1107 final String dnString = trailingArgsIterator.next(); 1108 try 1109 { 1110 new DN(dnString); 1111 } 1112 catch (final LDAPException e) 1113 { 1114 Debug.debugException(e); 1115 throw new LDAPException(ResultCode.PARAM_ERROR, 1116 ERR_LDAPCOMPARE_MALFORMED_TRAILING_ARG_DN.get(dnString, 1117 e.getMessage()), 1118 e); 1119 } 1120 1121 requests.add(new CompareRequest(dnString, attributeName, 1122 assertionValue, controls)); 1123 } 1124 1125 return requests; 1126 } 1127 } 1128 1129 1130 1131 /** 1132 * Parses the provided string as an attribute value assertion. It must 1133 * start with an attribute name or OID, and that must be followed by either a 1134 * single colon and the string representation of the assertion value, or 1135 * two colons and the base64-encoded representation of the assertion value. 1136 * 1137 * @param avaString The string to parse as an attribute value assertion. It 1138 * must not be {@code null}. 1139 * 1140 * @return An object pair in which the first element is the parsed attribute 1141 * name or OID, and the second element is the parsed assertion value. 1142 * 1143 * @throws LDAPException If the provided string cannot be parsed as a valid 1144 * attribute value assertion. 1145 */ 1146 @NotNull() 1147 private static ObjectPair<String,byte[]> parseAttributeValueAssertion( 1148 @NotNull final String avaString) 1149 throws LDAPException 1150 { 1151 final int colonPos = avaString.indexOf(':'); 1152 if (colonPos < 0) 1153 { 1154 throw new LDAPException(ResultCode.PARAM_ERROR, 1155 ERR_LDAPCOMPARE_AVA_NO_COLON.get(avaString)); 1156 } 1157 else if (colonPos == 0) 1158 { 1159 throw new LDAPException(ResultCode.PARAM_ERROR, 1160 ERR_LDAPCOMPARE_AVA_NO_ATTR.get(avaString)); 1161 } 1162 1163 final String attributeName = avaString.substring(0, colonPos); 1164 if (colonPos == (avaString.length() - 1)) 1165 { 1166 // This means that the assertion value is empty. 1167 return new ObjectPair<>(attributeName, StaticUtils.NO_BYTES); 1168 } 1169 1170 if (avaString.charAt(colonPos+1) == ':') 1171 { 1172 // This means that the assertion value is base64-encoded. 1173 try 1174 { 1175 final byte[] avaBytes = Base64.decode(avaString.substring(colonPos+2)); 1176 return new ObjectPair<>(attributeName, avaBytes); 1177 } 1178 catch (final Exception e) 1179 { 1180 Debug.debugException(e); 1181 throw new LDAPException(ResultCode.PARAM_ERROR, 1182 ERR_LDAPCOMPARE_AVA_CANNOT_BASE64_DECODE_VALUE.get(avaString, 1183 e.getMessage()), 1184 e); 1185 } 1186 } 1187 else if (avaString.charAt(colonPos+1) == '<') 1188 { 1189 // This means that the assertion value should be read from a file. The 1190 // path to that file should immediately follow the less-than symbol, and 1191 // the exact bytes contained in that file (including line breaks) will be 1192 // used as the assertion value. 1193 final String path = avaString.substring(colonPos+2); 1194 final File file = new File(path); 1195 if (file.exists()) 1196 { 1197 try 1198 { 1199 final byte[] fileBytes = StaticUtils.readFileBytes(file); 1200 return new ObjectPair<>(attributeName, fileBytes); 1201 } 1202 catch (final Exception e) 1203 { 1204 Debug.debugException(e); 1205 throw new LDAPException(ResultCode.LOCAL_ERROR, 1206 ERR_LDAPCOMPARE_AVA_CANNOT_READ_FILE.get(avaString, 1207 file.getAbsolutePath(), 1208 StaticUtils.getExceptionMessage(e)), 1209 e); 1210 } 1211 } 1212 else 1213 { 1214 throw new LDAPException(ResultCode.PARAM_ERROR, 1215 ERR_LDAPCOMPARE_AVA_NO_SUCH_FILE.get(avaString, 1216 file.getAbsolutePath())); 1217 } 1218 } 1219 1220 return new ObjectPair<>(attributeName, 1221 StaticUtils.getBytes(avaString.substring(colonPos+1))); 1222 } 1223 1224 1225 1226 /** 1227 * Reads the compare requests to process from the information in the 1228 * specified assertion file. Each line of the file must contain the DN of 1229 * the target entry followed by one or more tab characters and the 1230 * attribute-value assertion in the form expected by the 1231 * {@link #parseAttributeValueAssertion} method. Empty lines and lines that 1232 * start with the octothorpe (#) character will be ignored. 1233 * 1234 * @param controls The controls to include in each of the compare requests. 1235 * It must not be {@code null} but may be empty. 1236 * 1237 * @return A list of the compare requests that should be issued. 1238 * 1239 * @throws LDAPException If a problem is encountered while parsing the 1240 * contents of the assertion file. 1241 */ 1242 @NotNull() 1243 private List<CompareRequest> readAssertionFile( 1244 @NotNull final Control[] controls) 1245 throws LDAPException 1246 { 1247 final File f = assertionFile.getValue(); 1248 try (FileReader fileReader = new FileReader(f); 1249 BufferedReader bufferedReader = new BufferedReader(fileReader)) 1250 { 1251 int lineNumber = 0; 1252 final List<CompareRequest> compareRequests = new ArrayList<>(); 1253 while (true) 1254 { 1255 // Read the next line from the file. If it is null, then we've hit the 1256 // end fo the file. If the line is empty or starts with an octothorpe, 1257 // then skip it and read the next line. 1258 final String line = bufferedReader.readLine(); 1259 if (line == null) 1260 { 1261 if (compareRequests.isEmpty()) 1262 { 1263 throw new LDAPException(ResultCode.PARAM_ERROR, 1264 ERR_LDAPCOMPARE_ASSERTION_FILE_EMPTY.get(f.getAbsolutePath())); 1265 } 1266 1267 return compareRequests; 1268 } 1269 1270 lineNumber++; 1271 if (line.isEmpty() || line.startsWith("#")) 1272 { 1273 continue; 1274 } 1275 1276 1277 // Find the first tab on the line. Then, skip over any subsequent 1278 // tabs to find the assertion value. 1279 int tabPos = line.indexOf('\t'); 1280 if (tabPos < 0) 1281 { 1282 throw new LDAPException(ResultCode.DECODING_ERROR, 1283 ERR_LDAPCOMPARE_ASSERTION_FILE_LINE_MISSING_TAB.get( 1284 line, lineNumber, f.getAbsolutePath())); 1285 } 1286 1287 1288 final String dn = line.substring(0, tabPos); 1289 try 1290 { 1291 new DN(dn); 1292 } 1293 catch (final LDAPException e) 1294 { 1295 Debug.debugException(e); 1296 throw new LDAPException(ResultCode.DECODING_ERROR, 1297 ERR_LDAPCOMPARE_ASSERTION_FILE_LINE_INVALID_DN.get( 1298 line, lineNumber, f.getAbsolutePath(), dn, e.getMessage()), 1299 e); 1300 } 1301 1302 for (int i=(tabPos+1); i < line.length(); i++) 1303 { 1304 if (line.charAt(i) == '\t') 1305 { 1306 tabPos = i; 1307 } 1308 } 1309 1310 final String avaString = line.substring(tabPos+1); 1311 if (avaString.isEmpty()) 1312 { 1313 throw new LDAPException(ResultCode.DECODING_ERROR, 1314 ERR_LDAPCOMPARE_ASSERTION_FILE_LINE_MISSING_AVA.get( 1315 line, lineNumber, f.getAbsolutePath())); 1316 } 1317 1318 1319 final ObjectPair<String,byte[]> ava; 1320 try 1321 { 1322 ava = parseAttributeValueAssertion(avaString); 1323 } 1324 catch (final LDAPException e) 1325 { 1326 Debug.debugException(e); 1327 throw new LDAPException(ResultCode.DECODING_ERROR, 1328 ERR_LDAPCOMPARE_ASSERTION_FILE_CANNOT_PARSE_AVA.get( 1329 line, lineNumber, f.getAbsolutePath(), e.getMessage()), 1330 e); 1331 } 1332 1333 compareRequests.add(new CompareRequest(dn, ava.getFirst(), 1334 ava.getSecond(), controls)); 1335 } 1336 } 1337 catch (final IOException e) 1338 { 1339 Debug.debugException(e); 1340 throw new LDAPException(ResultCode.LOCAL_ERROR, 1341 ERR_LDAPCOMPARE_CANNOT_READ_ASSERTION_FILE.get( 1342 f.getAbsolutePath(), StaticUtils.getExceptionMessage(e)), 1343 e); 1344 } 1345 } 1346 1347 1348 1349 /** 1350 * Reads the DN file to obtain the DNs of the entries to target and creates 1351 * the list of compare requests to process. Each line of the file should 1352 * contain the DN of an entry to process. Empty lines and lines that start 1353 * with the octothorpe (#) character will be ignored. 1354 * 1355 * @param attributeName The name or OID of the attribute to target with 1356 * each of the compare requests. It must not be 1357 * {@code null} or empty. 1358 * @param assertionValue The assertion value to use for each of the 1359 * compare requests. It must not be {@code null}. 1360 * @param controls The controls to include in each of the compare 1361 * requests. It must not be {@code null} but may be 1362 * empty. 1363 * 1364 * @return A list of the compare requests that should be issued. 1365 * 1366 * @throws LDAPException If a problem is encountered while parsing the 1367 * contents of the assertion file. 1368 */ 1369 @NotNull() 1370 private List<CompareRequest> readDNFile(@NotNull final String attributeName, 1371 @NotNull final byte[] assertionValue, 1372 @NotNull final Control[] controls) 1373 throws LDAPException 1374 { 1375 try (DNFileReader dnFileReader = new DNFileReader(dnFile.getValue())) 1376 { 1377 final List<CompareRequest> compareRequests = new ArrayList<>(); 1378 while (true) 1379 { 1380 final DN dn; 1381 try 1382 { 1383 dn = dnFileReader.readDN(); 1384 } 1385 catch (final LDAPException e) 1386 { 1387 Debug.debugException(e); 1388 throw new LDAPException(ResultCode.DECODING_ERROR, e.getMessage(), e); 1389 } 1390 1391 if (dn == null) 1392 { 1393 if (compareRequests.isEmpty()) 1394 { 1395 throw new LDAPException(ResultCode.PARAM_ERROR, 1396 ERR_LDAPCOMPARE_DN_FILE_EMPTY.get( 1397 dnFile.getValue().getAbsolutePath())); 1398 } 1399 1400 return compareRequests; 1401 } 1402 1403 compareRequests.add(new CompareRequest(dn, attributeName, 1404 assertionValue, controls)); 1405 } 1406 } 1407 catch (final IOException e) 1408 { 1409 Debug.debugException(e); 1410 throw new LDAPException(ResultCode.LOCAL_ERROR, 1411 ERR_LDAPCOMPARE_CANNOT_READ_DN_FILE.get( 1412 dnFile.getValue().getAbsolutePath(), 1413 StaticUtils.getExceptionMessage(e)), 1414 e); 1415 } 1416 } 1417 1418 1419 1420 /** 1421 * Retrieves the controls that should be included in compare requests. 1422 * 1423 * @return The controls that should be included in compare requests, or an 1424 * empty array if no controls should be included. 1425 * 1426 * @throws LDAPException If a problem occurs while trying to create any of 1427 * the controls. 1428 */ 1429 @NotNull() 1430 private Control[] getCompareControls() 1431 throws LDAPException 1432 { 1433 final List<Control> controls = new ArrayList<>(); 1434 1435 if (compareControl.isPresent()) 1436 { 1437 controls.addAll(compareControl.getValues()); 1438 } 1439 1440 if (proxyAs.isPresent()) 1441 { 1442 controls.add(new ProxiedAuthorizationV2RequestControl( 1443 proxyAs.getValue())); 1444 } 1445 1446 if (proxyV1As.isPresent()) 1447 { 1448 controls.add(new ProxiedAuthorizationV1RequestControl( 1449 proxyV1As.getValue())); 1450 } 1451 1452 if (manageDsaIT.isPresent()) 1453 { 1454 controls.add(new ManageDsaITRequestControl(false)); 1455 } 1456 1457 if (assertionFilter.isPresent()) 1458 { 1459 controls.add(new AssertionRequestControl(assertionFilter.getValue())); 1460 } 1461 1462 if (operationPurpose.isPresent()) 1463 { 1464 controls.add(new OperationPurposeRequestControl(false, getToolName(), 1465 getToolVersion(), 1466 LDAPPasswordModify.class.getName() + ".getUpdateControls", 1467 operationPurpose.getValue())); 1468 } 1469 1470 return controls.toArray(StaticUtils.NO_CONTROLS); 1471 } 1472 1473 1474 1475 /** 1476 * Writes information about the compare result. 1477 * 1478 * @param writer The writer to use to write to the output file. It 1479 * may be {@code null} if no output file should be 1480 * used. 1481 * @param outputHandler The output handler that should be used to format the 1482 * result information. It must not be {@code null}. 1483 * @param request The compare request that was processed. It must not 1484 * be {@code null}. 1485 * @param result The result for the compare operation. It must not 1486 * be {@code null}. 1487 * 1488 * @throws LDAPException If a problem occurred while trying to write the 1489 * result. 1490 */ 1491 private void writeResult(@Nullable final PrintStream writer, 1492 @NotNull final LDAPCompareOutputHandler outputHandler, 1493 @NotNull final CompareRequest request, 1494 @NotNull final LDAPResult result) 1495 throws LDAPException 1496 { 1497 if (shouldWriteResultToStdErr(result)) 1498 { 1499 err(); 1500 err(INFO_LDAPCOMPARE_RESULT_HEADER.get()); 1501 err(INFO_LDAPCOMPARE_RESULT_HEADER_DN.get(request.getDN())); 1502 err(INFO_LDAPCOMPARE_RESULT_HEADER_ATTR.get(request.getAttributeName())); 1503 err(INFO_LDAPCOMPARE_RESULT_HEADER_VALUE.get( 1504 request.getAssertionValue())); 1505 for (final String line : ResultUtils.formatResult(result, true, 0, 1506 WRAP_COLUMN)) 1507 { 1508 err(line); 1509 } 1510 } 1511 1512 final String message = outputHandler.formatResult(request, result); 1513 if (writer != null) 1514 { 1515 writer.println(message); 1516 } 1517 1518 if ((writer == null) || teeOutput.isPresent()) 1519 { 1520 out(message); 1521 } 1522 } 1523 1524 1525 1526 /** 1527 * Indicates whether to write information about the provided result to 1528 * standard error. 1529 * 1530 * @param result The result for which to make the determination. It must 1531 * not be {@code mull}. 1532 * 1533 * @return {@code true} if information about the result should be written to 1534 * standard error, or {@code false} if not. 1535 */ 1536 private boolean shouldWriteResultToStdErr(@NotNull final LDAPResult result) 1537 { 1538 if (verbose.isPresent()) 1539 { 1540 return true; 1541 } 1542 1543 if (terse.isPresent()) 1544 { 1545 return false; 1546 } 1547 1548 if (result.hasResponseControl()) 1549 { 1550 return true; 1551 } 1552 1553 return ((result.getResultCode() != ResultCode.COMPARE_TRUE) && 1554 (result.getResultCode() != ResultCode.COMPARE_FALSE)); 1555 } 1556 1557 1558 1559 /** 1560 * Writes the provided message and sets it as the completion message. 1561 * 1562 * @param isError Indicates whether the message should be written to 1563 * standard error rather than standard output. 1564 * @param message The message to be written. 1565 */ 1566 private void logCompletionMessage(final boolean isError, 1567 @NotNull final String message) 1568 { 1569 completionMessage.compareAndSet(null, message); 1570 1571 if (! terse.isPresent()) 1572 { 1573 if (isError) 1574 { 1575 wrapErr(0, WRAP_COLUMN, message); 1576 } 1577 else 1578 { 1579 wrapOut(0, WRAP_COLUMN, message); 1580 } 1581 } 1582 } 1583 1584 1585 1586 /** 1587 * {@inheritDoc} 1588 */ 1589 @Override() 1590 public void handleUnsolicitedNotification( 1591 @NotNull final LDAPConnection connection, 1592 @NotNull final ExtendedResult notification) 1593 { 1594 if (! terse.isPresent()) 1595 { 1596 final ArrayList<String> lines = new ArrayList<>(10); 1597 ResultUtils.formatUnsolicitedNotification(lines, notification, true, 0, 1598 WRAP_COLUMN); 1599 for (final String line : lines) 1600 { 1601 err(line); 1602 } 1603 err(); 1604 } 1605 } 1606 1607 1608 1609 /** 1610 * {@inheritDoc} 1611 */ 1612 @Override() 1613 @NotNull() 1614 public LinkedHashMap<String[],String> getExampleUsages() 1615 { 1616 final LinkedHashMap<String[],String> examples = new LinkedHashMap<>(); 1617 1618 examples.put( 1619 new String[] 1620 { 1621 "--hostname", "ds.example.com", 1622 "--port", "636", 1623 "--useSSL", 1624 "--bindDN", "uid=admin,dc=example,dc=com", 1625 "l:Austin", 1626 "uid=jdoe,ou=People,dc=example,dc=com" 1627 }, 1628 INFO_LDAPCOMPARE_EXAMPLE_1.get()); 1629 1630 examples.put( 1631 new String[] 1632 { 1633 "--hostname", "ds.example.com", 1634 "--port", "636", 1635 "--useSSL", 1636 "--bindDN", "uid=admin,dc=example,dc=com", 1637 "--dnFile", "entry-dns.txt", 1638 "--outputFormat", "csv", 1639 "--terse", 1640 "title:manager" 1641 }, 1642 INFO_LDAPCOMPARE_EXAMPLE_2.get()); 1643 1644 examples.put( 1645 new String[] 1646 { 1647 "--hostname", "ds.example.com", 1648 "--port", "636", 1649 "--useSSL", 1650 "--bindDN", "uid=admin,dc=example,dc=com", 1651 "--assertionFile", "compare-assertions.txt", 1652 "--outputFormat", "json", 1653 "--outputFile", "compare-assertion-results.json", 1654 "--verbose" 1655 }, 1656 INFO_LDAPCOMPARE_EXAMPLE_3.get()); 1657 1658 return examples; 1659 } 1660}