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 PrintStream writer = null; 803 try 804 { 805 // Create a connection pool that will be used to communicate with the 806 // directory server. If we should use an administrative session, then 807 // create a connect processor that will be used to start the session 808 // before performing the bind. 809 try 810 { 811 final StartAdministrativeSessionPostConnectProcessor p; 812 if (useAdministrativeSession.isPresent()) 813 { 814 p = new StartAdministrativeSessionPostConnectProcessor( 815 new StartAdministrativeSessionExtendedRequest(getToolName(), 816 true)); 817 } 818 else 819 { 820 p = null; 821 } 822 823 pool = getConnectionPool(1, 2, 0, p, null, true, 824 new ReportBindResultLDAPConnectionPoolHealthCheck(this, true, 825 verbose.isPresent())); 826 pool.setRetryFailedOperationsDueToInvalidConnections(true); 827 828 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 if (writer != null) 1028 { 1029 writer.close(); 1030 } 1031 } 1032 } 1033 1034 1035 1036 /** 1037 * Retrieves a list of the compare requests that should be issued. 1038 * 1039 * @return A list of the compare requests that should be issued. 1040 * 1041 * @throws LDAPException If a problem occurs while obtaining the compare 1042 * requests to process. 1043 */ 1044 @NotNull() 1045 private List<CompareRequest> getCompareRequests() 1046 throws LDAPException 1047 { 1048 final List<String> trailingArgs = argumentParser.getTrailingArguments(); 1049 final int numTrailingArgs = trailingArgs.size(); 1050 1051 if (assertionFile.isPresent()) 1052 { 1053 if (numTrailingArgs != 0) 1054 { 1055 throw new LDAPException(ResultCode.PARAM_ERROR, 1056 ERR_LDAPCOMPARE_TRAILING_ARGS_WITH_ASSERTION_FILE.get( 1057 assertionFile.getIdentifierString())); 1058 } 1059 1060 return readAssertionFile(getCompareControls()); 1061 } 1062 else if (dnFile.isPresent()) 1063 { 1064 if (numTrailingArgs != 1) 1065 { 1066 throw new LDAPException(ResultCode.PARAM_ERROR, 1067 ERR_LDAPCOMPARE_INVALID_TRAILING_ARG_COUNT_WITH_DN_FILE.get( 1068 dnFile.getIdentifierString())); 1069 } 1070 1071 final ObjectPair<String,byte[]> ava = 1072 parseAttributeValueAssertion(trailingArgs.get(0)); 1073 return readDNFile(ava.getFirst(), ava.getSecond(), getCompareControls()); 1074 } 1075 else 1076 { 1077 if (numTrailingArgs < 2) 1078 { 1079 throw new LDAPException(ResultCode.PARAM_ERROR, 1080 ERR_LDAPCOMPARE_INVALID_TRAILING_ARG_COUNT_WITHOUT_FILE.get( 1081 dnFile.getIdentifierString(), 1082 assertionFile.getIdentifierString())); 1083 } 1084 1085 final Iterator<String> trailingArgsIterator = trailingArgs.iterator(); 1086 final ObjectPair<String,byte[]> ava = 1087 parseAttributeValueAssertion(trailingArgsIterator.next()); 1088 final String attributeName = ava.getFirst(); 1089 final byte[] assertionValue = ava.getSecond(); 1090 1091 final Control[] controls = getCompareControls(); 1092 1093 final List<CompareRequest> requests = new ArrayList<>(numTrailingArgs-1); 1094 while (trailingArgsIterator.hasNext()) 1095 { 1096 final String dnString = trailingArgsIterator.next(); 1097 try 1098 { 1099 new DN(dnString); 1100 } 1101 catch (final LDAPException e) 1102 { 1103 Debug.debugException(e); 1104 throw new LDAPException(ResultCode.PARAM_ERROR, 1105 ERR_LDAPCOMPARE_MALFORMED_TRAILING_ARG_DN.get(dnString, 1106 e.getMessage()), 1107 e); 1108 } 1109 1110 requests.add(new CompareRequest(dnString, attributeName, 1111 assertionValue, controls)); 1112 } 1113 1114 return requests; 1115 } 1116 } 1117 1118 1119 1120 /** 1121 * Parses the provided string as an attribute value assertion. It must 1122 * start with an attribute name or OID, and that must be followed by either a 1123 * single colon and the string representation of the assertion value, or 1124 * two colons and the base64-encoded representation of the assertion value. 1125 * 1126 * @param avaString The string to parse as an attribute value assertion. It 1127 * must not be {@code null}. 1128 * 1129 * @return An object pair in which the first element is the parsed attribute 1130 * name or OID, and the second element is the parsed assertion value. 1131 * 1132 * @throws LDAPException If the provided string cannot be parsed as a valid 1133 * attribute value assertion. 1134 */ 1135 @NotNull() 1136 private static ObjectPair<String,byte[]> parseAttributeValueAssertion( 1137 @NotNull final String avaString) 1138 throws LDAPException 1139 { 1140 final int colonPos = avaString.indexOf(':'); 1141 if (colonPos < 0) 1142 { 1143 throw new LDAPException(ResultCode.PARAM_ERROR, 1144 ERR_LDAPCOMPARE_AVA_NO_COLON.get(avaString)); 1145 } 1146 else if (colonPos == 0) 1147 { 1148 throw new LDAPException(ResultCode.PARAM_ERROR, 1149 ERR_LDAPCOMPARE_AVA_NO_ATTR.get(avaString)); 1150 } 1151 1152 final String attributeName = avaString.substring(0, colonPos); 1153 if (colonPos == (avaString.length() - 1)) 1154 { 1155 // This means that the assertion value is empty. 1156 return new ObjectPair<>(attributeName, StaticUtils.NO_BYTES); 1157 } 1158 1159 if (avaString.charAt(colonPos+1) == ':') 1160 { 1161 // This means that the assertion value is base64-encoded. 1162 try 1163 { 1164 final byte[] avaBytes = Base64.decode(avaString.substring(colonPos+2)); 1165 return new ObjectPair<>(attributeName, avaBytes); 1166 } 1167 catch (final Exception e) 1168 { 1169 Debug.debugException(e); 1170 throw new LDAPException(ResultCode.PARAM_ERROR, 1171 ERR_LDAPCOMPARE_AVA_CANNOT_BASE64_DECODE_VALUE.get(avaString, 1172 e.getMessage()), 1173 e); 1174 } 1175 } 1176 else if (avaString.charAt(colonPos+1) == '<') 1177 { 1178 // This means that the assertion value should be read from a file. The 1179 // path to that file should immediately follow the less-than symbol, and 1180 // the exact bytes contained in that file (including line breaks) will be 1181 // used as the assertion value. 1182 final String path = avaString.substring(colonPos+2); 1183 final File file = new File(path); 1184 if (file.exists()) 1185 { 1186 try 1187 { 1188 final byte[] fileBytes = StaticUtils.readFileBytes(file); 1189 return new ObjectPair<>(attributeName, fileBytes); 1190 } 1191 catch (final Exception e) 1192 { 1193 Debug.debugException(e); 1194 throw new LDAPException(ResultCode.LOCAL_ERROR, 1195 ERR_LDAPCOMPARE_AVA_CANNOT_READ_FILE.get(avaString, 1196 file.getAbsolutePath(), 1197 StaticUtils.getExceptionMessage(e)), 1198 e); 1199 } 1200 } 1201 else 1202 { 1203 throw new LDAPException(ResultCode.PARAM_ERROR, 1204 ERR_LDAPCOMPARE_AVA_NO_SUCH_FILE.get(avaString, 1205 file.getAbsolutePath())); 1206 } 1207 } 1208 1209 return new ObjectPair<>(attributeName, 1210 StaticUtils.getBytes(avaString.substring(colonPos+1))); 1211 } 1212 1213 1214 1215 /** 1216 * Reads the compare requests to process from the information in the 1217 * specified assertion file. Each line of the file must contain the DN of 1218 * the target entry followed by one or more tab characters and the 1219 * attribute-value assertion in the form expected by the 1220 * {@link #parseAttributeValueAssertion} method. Empty lines and lines that 1221 * start with the octothorpe (#) character will be ignored. 1222 * 1223 * @param controls The controls to include in each of the compare requests. 1224 * It must not be {@code null} but may be empty. 1225 * 1226 * @return A list of the compare requests that should be issued. 1227 * 1228 * @throws LDAPException If a problem is encountered while parsing the 1229 * contents of the assertion file. 1230 */ 1231 @NotNull() 1232 private List<CompareRequest> readAssertionFile( 1233 @NotNull final Control[] controls) 1234 throws LDAPException 1235 { 1236 final File f = assertionFile.getValue(); 1237 try (FileReader fileReader = new FileReader(f); 1238 BufferedReader bufferedReader = new BufferedReader(fileReader)) 1239 { 1240 int lineNumber = 0; 1241 final List<CompareRequest> compareRequests = new ArrayList<>(); 1242 while (true) 1243 { 1244 // Read the next line from the file. If it is null, then we've hit the 1245 // end fo the file. If the line is empty or starts with an octothorpe, 1246 // then skip it and read the next line. 1247 final String line = bufferedReader.readLine(); 1248 if (line == null) 1249 { 1250 if (compareRequests.isEmpty()) 1251 { 1252 throw new LDAPException(ResultCode.PARAM_ERROR, 1253 ERR_LDAPCOMPARE_ASSERTION_FILE_EMPTY.get(f.getAbsolutePath())); 1254 } 1255 1256 return compareRequests; 1257 } 1258 1259 lineNumber++; 1260 if (line.isEmpty() || line.startsWith("#")) 1261 { 1262 continue; 1263 } 1264 1265 1266 // Find the first tab on the line. Then, skip over any subsequent 1267 // tabs to find the assertion value. 1268 int tabPos = line.indexOf('\t'); 1269 if (tabPos < 0) 1270 { 1271 throw new LDAPException(ResultCode.DECODING_ERROR, 1272 ERR_LDAPCOMPARE_ASSERTION_FILE_LINE_MISSING_TAB.get( 1273 line, lineNumber, f.getAbsolutePath())); 1274 } 1275 1276 1277 final String dn = line.substring(0, tabPos); 1278 try 1279 { 1280 new DN(dn); 1281 } 1282 catch (final LDAPException e) 1283 { 1284 Debug.debugException(e); 1285 throw new LDAPException(ResultCode.DECODING_ERROR, 1286 ERR_LDAPCOMPARE_ASSERTION_FILE_LINE_INVALID_DN.get( 1287 line, lineNumber, f.getAbsolutePath(), dn, e.getMessage()), 1288 e); 1289 } 1290 1291 for (int i=(tabPos+1); i < line.length(); i++) 1292 { 1293 if (line.charAt(i) == '\t') 1294 { 1295 tabPos = i; 1296 } 1297 } 1298 1299 final String avaString = line.substring(tabPos+1); 1300 if (avaString.isEmpty()) 1301 { 1302 throw new LDAPException(ResultCode.DECODING_ERROR, 1303 ERR_LDAPCOMPARE_ASSERTION_FILE_LINE_MISSING_AVA.get( 1304 line, lineNumber, f.getAbsolutePath())); 1305 } 1306 1307 1308 final ObjectPair<String,byte[]> ava; 1309 try 1310 { 1311 ava = parseAttributeValueAssertion(avaString); 1312 } 1313 catch (final LDAPException e) 1314 { 1315 Debug.debugException(e); 1316 throw new LDAPException(ResultCode.DECODING_ERROR, 1317 ERR_LDAPCOMPARE_ASSERTION_FILE_CANNOT_PARSE_AVA.get( 1318 line, lineNumber, f.getAbsolutePath(), e.getMessage()), 1319 e); 1320 } 1321 1322 compareRequests.add(new CompareRequest(dn, ava.getFirst(), 1323 ava.getSecond(), controls)); 1324 } 1325 } 1326 catch (final IOException e) 1327 { 1328 Debug.debugException(e); 1329 throw new LDAPException(ResultCode.LOCAL_ERROR, 1330 ERR_LDAPCOMPARE_CANNOT_READ_ASSERTION_FILE.get( 1331 f.getAbsolutePath(), StaticUtils.getExceptionMessage(e)), 1332 e); 1333 } 1334 } 1335 1336 1337 1338 /** 1339 * Reads the DN file to obtain the DNs of the entries to target and creates 1340 * the list of compare requests to process. Each line of the file should 1341 * contain the DN of an entry to process. Empty lines and lines that start 1342 * with the octothorpe (#) character will be ignored. 1343 * 1344 * @param attributeName The name or OID of the attribute to target with 1345 * each of the compare requests. It must not be 1346 * {@code null} or empty. 1347 * @param assertionValue The assertion value to use for each of the 1348 * compare requests. It must not be {@code null}. 1349 * @param controls The controls to include in each of the compare 1350 * requests. It must not be {@code null} but may be 1351 * empty. 1352 * 1353 * @return A list of the compare requests that should be issued. 1354 * 1355 * @throws LDAPException If a problem is encountered while parsing the 1356 * contents of the assertion file. 1357 */ 1358 @NotNull() 1359 private List<CompareRequest> readDNFile(@NotNull final String attributeName, 1360 @NotNull final byte[] assertionValue, 1361 @NotNull final Control[] controls) 1362 throws LDAPException 1363 { 1364 try (DNFileReader dnFileReader = new DNFileReader(dnFile.getValue())) 1365 { 1366 final List<CompareRequest> compareRequests = new ArrayList<>(); 1367 while (true) 1368 { 1369 final DN dn; 1370 try 1371 { 1372 dn = dnFileReader.readDN(); 1373 } 1374 catch (final LDAPException e) 1375 { 1376 Debug.debugException(e); 1377 throw new LDAPException(ResultCode.DECODING_ERROR, e.getMessage(), e); 1378 } 1379 1380 if (dn == null) 1381 { 1382 if (compareRequests.isEmpty()) 1383 { 1384 throw new LDAPException(ResultCode.PARAM_ERROR, 1385 ERR_LDAPCOMPARE_DN_FILE_EMPTY.get( 1386 dnFile.getValue().getAbsolutePath())); 1387 } 1388 1389 return compareRequests; 1390 } 1391 1392 compareRequests.add(new CompareRequest(dn, attributeName, 1393 assertionValue, controls)); 1394 } 1395 } 1396 catch (final IOException e) 1397 { 1398 Debug.debugException(e); 1399 throw new LDAPException(ResultCode.LOCAL_ERROR, 1400 ERR_LDAPCOMPARE_CANNOT_READ_DN_FILE.get( 1401 dnFile.getValue().getAbsolutePath(), 1402 StaticUtils.getExceptionMessage(e)), 1403 e); 1404 } 1405 } 1406 1407 1408 1409 /** 1410 * Retrieves the controls that should be included in compare requests. 1411 * 1412 * @return The controls that should be included in compare requests, or an 1413 * empty array if no controls should be included. 1414 * 1415 * @throws LDAPException If a problem occurs while trying to create any of 1416 * the controls. 1417 */ 1418 @NotNull() 1419 private Control[] getCompareControls() 1420 throws LDAPException 1421 { 1422 final List<Control> controls = new ArrayList<>(); 1423 1424 if (compareControl.isPresent()) 1425 { 1426 controls.addAll(compareControl.getValues()); 1427 } 1428 1429 if (proxyAs.isPresent()) 1430 { 1431 controls.add(new ProxiedAuthorizationV2RequestControl( 1432 proxyAs.getValue())); 1433 } 1434 1435 if (proxyV1As.isPresent()) 1436 { 1437 controls.add(new ProxiedAuthorizationV1RequestControl( 1438 proxyV1As.getValue())); 1439 } 1440 1441 if (manageDsaIT.isPresent()) 1442 { 1443 controls.add(new ManageDsaITRequestControl(false)); 1444 } 1445 1446 if (assertionFilter.isPresent()) 1447 { 1448 controls.add(new AssertionRequestControl(assertionFilter.getValue())); 1449 } 1450 1451 if (operationPurpose.isPresent()) 1452 { 1453 controls.add(new OperationPurposeRequestControl(false, getToolName(), 1454 getToolVersion(), 1455 LDAPPasswordModify.class.getName() + ".getUpdateControls", 1456 operationPurpose.getValue())); 1457 } 1458 1459 return controls.toArray(StaticUtils.NO_CONTROLS); 1460 } 1461 1462 1463 1464 /** 1465 * Writes information about the compare result. 1466 * 1467 * @param writer The writer to use to write to the output file. It 1468 * may be {@code null} if no output file should be 1469 * used. 1470 * @param outputHandler The output handler that should be used to format the 1471 * result information. It must not be {@code null}. 1472 * @param request The compare request that was processed. It must not 1473 * be {@code null}. 1474 * @param result The result for the compare operation. It must not 1475 * be {@code null}. 1476 * 1477 * @throws LDAPException If a problem occurred while trying to write the 1478 * result. 1479 */ 1480 private void writeResult(@Nullable final PrintStream writer, 1481 @NotNull final LDAPCompareOutputHandler outputHandler, 1482 @NotNull final CompareRequest request, 1483 @NotNull final LDAPResult result) 1484 throws LDAPException 1485 { 1486 if (shouldWriteResultToStdErr(result)) 1487 { 1488 err(); 1489 err(INFO_LDAPCOMPARE_RESULT_HEADER.get()); 1490 err(INFO_LDAPCOMPARE_RESULT_HEADER_DN.get(request.getDN())); 1491 err(INFO_LDAPCOMPARE_RESULT_HEADER_ATTR.get(request.getAttributeName())); 1492 err(INFO_LDAPCOMPARE_RESULT_HEADER_VALUE.get( 1493 request.getAssertionValue())); 1494 for (final String line : ResultUtils.formatResult(result, true, 0, 1495 WRAP_COLUMN)) 1496 { 1497 err(line); 1498 } 1499 } 1500 1501 final String message = outputHandler.formatResult(request, result); 1502 if (writer != null) 1503 { 1504 writer.println(message); 1505 } 1506 1507 if ((writer == null) || teeOutput.isPresent()) 1508 { 1509 out(message); 1510 } 1511 } 1512 1513 1514 1515 /** 1516 * Indicates whether to write information about the provided result to 1517 * standard error. 1518 * 1519 * @param result The result for which to make the determination. It must 1520 * not be {@code mull}. 1521 * 1522 * @return {@code true} if information about the result should be written to 1523 * standard error, or {@code false} if not. 1524 */ 1525 private boolean shouldWriteResultToStdErr(@NotNull final LDAPResult result) 1526 { 1527 if (verbose.isPresent()) 1528 { 1529 return true; 1530 } 1531 1532 if (terse.isPresent()) 1533 { 1534 return false; 1535 } 1536 1537 if (result.hasResponseControl()) 1538 { 1539 return true; 1540 } 1541 1542 return ((result.getResultCode() != ResultCode.COMPARE_TRUE) && 1543 (result.getResultCode() != ResultCode.COMPARE_FALSE)); 1544 } 1545 1546 1547 1548 /** 1549 * Writes the provided message and sets it as the completion message. 1550 * 1551 * @param isError Indicates whether the message should be written to 1552 * standard error rather than standard output. 1553 * @param message The message to be written. 1554 */ 1555 private void logCompletionMessage(final boolean isError, 1556 @NotNull final String message) 1557 { 1558 completionMessage.compareAndSet(null, message); 1559 1560 if (! terse.isPresent()) 1561 { 1562 if (isError) 1563 { 1564 wrapErr(0, WRAP_COLUMN, message); 1565 } 1566 else 1567 { 1568 wrapOut(0, WRAP_COLUMN, message); 1569 } 1570 } 1571 } 1572 1573 1574 1575 /** 1576 * {@inheritDoc} 1577 */ 1578 @Override() 1579 public void handleUnsolicitedNotification( 1580 @NotNull final LDAPConnection connection, 1581 @NotNull final ExtendedResult notification) 1582 { 1583 if (! terse.isPresent()) 1584 { 1585 final ArrayList<String> lines = new ArrayList<>(10); 1586 ResultUtils.formatUnsolicitedNotification(lines, notification, true, 0, 1587 WRAP_COLUMN); 1588 for (final String line : lines) 1589 { 1590 err(line); 1591 } 1592 err(); 1593 } 1594 } 1595 1596 1597 1598 /** 1599 * {@inheritDoc} 1600 */ 1601 @Override() 1602 @NotNull() 1603 public LinkedHashMap<String[],String> getExampleUsages() 1604 { 1605 final LinkedHashMap<String[],String> examples = new LinkedHashMap<>(); 1606 1607 examples.put( 1608 new String[] 1609 { 1610 "--hostname", "ds.example.com", 1611 "--port", "636", 1612 "--useSSL", 1613 "--bindDN", "uid=admin,dc=example,dc=com", 1614 "l:Austin", 1615 "uid=jdoe,ou=People,dc=example,dc=com" 1616 }, 1617 INFO_LDAPCOMPARE_EXAMPLE_1.get()); 1618 1619 examples.put( 1620 new String[] 1621 { 1622 "--hostname", "ds.example.com", 1623 "--port", "636", 1624 "--useSSL", 1625 "--bindDN", "uid=admin,dc=example,dc=com", 1626 "--dnFile", "entry-dns.txt", 1627 "--outputFormat", "csv", 1628 "--terse", 1629 "title:manager" 1630 }, 1631 INFO_LDAPCOMPARE_EXAMPLE_2.get()); 1632 1633 examples.put( 1634 new String[] 1635 { 1636 "--hostname", "ds.example.com", 1637 "--port", "636", 1638 "--useSSL", 1639 "--bindDN", "uid=admin,dc=example,dc=com", 1640 "--assertionFile", "compare-assertions.txt", 1641 "--outputFormat", "json", 1642 "--outputFile", "compare-assertion-results.json", 1643 "--verbose" 1644 }, 1645 INFO_LDAPCOMPARE_EXAMPLE_3.get()); 1646 1647 return examples; 1648 } 1649}