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