001/* 002 * Copyright 2017-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2017-2024 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2017-2024 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.ldap.sdk.unboundidds.tools; 037 038 039 040import java.io.BufferedReader; 041import java.io.File; 042import java.io.FileOutputStream; 043import java.io.FileReader; 044import java.io.IOException; 045import java.io.OutputStream; 046import java.io.PrintStream; 047import java.math.BigDecimal; 048import java.util.ArrayList; 049import java.util.Arrays; 050import java.util.Collections; 051import java.util.EnumSet; 052import java.util.Iterator; 053import java.util.LinkedHashMap; 054import java.util.List; 055import java.util.Map; 056import java.util.Set; 057import java.util.StringTokenizer; 058import java.util.concurrent.atomic.AtomicLong; 059import java.util.zip.GZIPOutputStream; 060 061import com.unboundid.asn1.ASN1OctetString; 062import com.unboundid.ldap.sdk.Control; 063import com.unboundid.ldap.sdk.DN; 064import com.unboundid.ldap.sdk.DereferencePolicy; 065import com.unboundid.ldap.sdk.ExtendedResult; 066import com.unboundid.ldap.sdk.Filter; 067import com.unboundid.ldap.sdk.LDAPConnectionOptions; 068import com.unboundid.ldap.sdk.LDAPConnection; 069import com.unboundid.ldap.sdk.LDAPConnectionPool; 070import com.unboundid.ldap.sdk.LDAPException; 071import com.unboundid.ldap.sdk.LDAPResult; 072import com.unboundid.ldap.sdk.LDAPSearchException; 073import com.unboundid.ldap.sdk.LDAPURL; 074import com.unboundid.ldap.sdk.ResultCode; 075import com.unboundid.ldap.sdk.SearchRequest; 076import com.unboundid.ldap.sdk.SearchResult; 077import com.unboundid.ldap.sdk.SearchScope; 078import com.unboundid.ldap.sdk.UnsolicitedNotificationHandler; 079import com.unboundid.ldap.sdk.Version; 080import com.unboundid.ldap.sdk.controls.AssertionRequestControl; 081import com.unboundid.ldap.sdk.controls.AuthorizationIdentityRequestControl; 082import com.unboundid.ldap.sdk.controls.DraftLDUPSubentriesRequestControl; 083import com.unboundid.ldap.sdk.controls.ManageDsaITRequestControl; 084import com.unboundid.ldap.sdk.controls.MatchedValuesFilter; 085import com.unboundid.ldap.sdk.controls.MatchedValuesRequestControl; 086import com.unboundid.ldap.sdk.controls.PersistentSearchChangeType; 087import com.unboundid.ldap.sdk.controls.PersistentSearchRequestControl; 088import com.unboundid.ldap.sdk.controls.ProxiedAuthorizationV1RequestControl; 089import com.unboundid.ldap.sdk.controls.ProxiedAuthorizationV2RequestControl; 090import com.unboundid.ldap.sdk.controls.RFC3672SubentriesRequestControl; 091import com.unboundid.ldap.sdk.controls.ServerSideSortRequestControl; 092import com.unboundid.ldap.sdk.controls.SimplePagedResultsControl; 093import com.unboundid.ldap.sdk.controls.SortKey; 094import com.unboundid.ldap.sdk.controls.VirtualListViewRequestControl; 095import com.unboundid.ldap.sdk.persist.PersistUtils; 096import com.unboundid.ldap.sdk.transformations.EntryTransformation; 097import com.unboundid.ldap.sdk.transformations.ExcludeAttributeTransformation; 098import com.unboundid.ldap.sdk.transformations.MoveSubtreeTransformation; 099import com.unboundid.ldap.sdk.transformations.RedactAttributeTransformation; 100import com.unboundid.ldap.sdk.transformations.RenameAttributeTransformation; 101import com.unboundid.ldap.sdk.transformations.ScrambleAttributeTransformation; 102import com.unboundid.ldap.sdk.unboundidds.controls.AccessLogFieldRequestControl; 103import com.unboundid.ldap.sdk.unboundidds.controls.AccountUsableRequestControl; 104import com.unboundid.ldap.sdk.unboundidds.controls.ExcludeBranchRequestControl; 105import com.unboundid.ldap.sdk.unboundidds.controls. 106 GenerateAccessTokenRequestControl; 107import com.unboundid.ldap.sdk.unboundidds.controls. 108 GetAuthorizationEntryRequestControl; 109import com.unboundid.ldap.sdk.unboundidds.controls. 110 GetBackendSetIDRequestControl; 111import com.unboundid.ldap.sdk.unboundidds.controls. 112 GetEffectiveRightsRequestControl; 113import com.unboundid.ldap.sdk.unboundidds.controls. 114 GetRecentLoginHistoryRequestControl; 115import com.unboundid.ldap.sdk.unboundidds.controls.GetServerIDRequestControl; 116import com.unboundid.ldap.sdk.unboundidds.controls. 117 GetUserResourceLimitsRequestControl; 118import com.unboundid.ldap.sdk.unboundidds.controls. 119 JSONFormattedControlDecodeBehavior; 120import com.unboundid.ldap.sdk.unboundidds.controls.JSONFormattedRequestControl; 121import com.unboundid.ldap.sdk.unboundidds.controls.JSONFormattedResponseControl; 122import com.unboundid.ldap.sdk.unboundidds.controls.JoinBaseDN; 123import com.unboundid.ldap.sdk.unboundidds.controls.JoinRequestControl; 124import com.unboundid.ldap.sdk.unboundidds.controls.JoinRequestValue; 125import com.unboundid.ldap.sdk.unboundidds.controls.JoinRule; 126import com.unboundid.ldap.sdk.unboundidds.controls. 127 MatchingEntryCountRequestControl; 128import com.unboundid.ldap.sdk.unboundidds.controls. 129 MatchingEntryCountRequestControlProperties; 130import com.unboundid.ldap.sdk.unboundidds.controls. 131 OperationPurposeRequestControl; 132import com.unboundid.ldap.sdk.unboundidds.controls. 133 OverrideSearchLimitsRequestControl; 134import com.unboundid.ldap.sdk.unboundidds.controls.PasswordPolicyRequestControl; 135import com.unboundid.ldap.sdk.unboundidds.controls. 136 PermitUnindexedSearchRequestControl; 137import com.unboundid.ldap.sdk.unboundidds.controls. 138 RealAttributesOnlyRequestControl; 139import com.unboundid.ldap.sdk.unboundidds.controls. 140 RejectUnindexedSearchRequestControl; 141import com.unboundid.ldap.sdk.unboundidds.controls. 142 ReturnConflictEntriesRequestControl; 143import com.unboundid.ldap.sdk.unboundidds.controls. 144 RouteToBackendSetRequestControl; 145import com.unboundid.ldap.sdk.unboundidds.controls.RouteToServerRequestControl; 146import com.unboundid.ldap.sdk.unboundidds.controls. 147 SoftDeletedEntryAccessRequestControl; 148import com.unboundid.ldap.sdk.unboundidds.controls. 149 SuppressOperationalAttributeUpdateRequestControl; 150import com.unboundid.ldap.sdk.unboundidds.controls.SuppressType; 151import com.unboundid.ldap.sdk.unboundidds.controls. 152 VirtualAttributesOnlyRequestControl; 153import com.unboundid.ldap.sdk.unboundidds.extensions. 154 StartAdministrativeSessionExtendedRequest; 155import com.unboundid.ldap.sdk.unboundidds.extensions. 156 StartAdministrativeSessionPostConnectProcessor; 157import com.unboundid.ldif.LDIFWriter; 158import com.unboundid.util.Debug; 159import com.unboundid.util.FilterFileReader; 160import com.unboundid.util.FixedRateBarrier; 161import com.unboundid.util.LDAPCommandLineTool; 162import com.unboundid.util.NotNull; 163import com.unboundid.util.Nullable; 164import com.unboundid.util.OutputFormat; 165import com.unboundid.util.PassphraseEncryptedOutputStream; 166import com.unboundid.util.StaticUtils; 167import com.unboundid.util.TeeOutputStream; 168import com.unboundid.util.ThreadSafety; 169import com.unboundid.util.ThreadSafetyLevel; 170import com.unboundid.util.args.ArgumentException; 171import com.unboundid.util.args.ArgumentParser; 172import com.unboundid.util.args.BooleanArgument; 173import com.unboundid.util.args.BooleanValueArgument; 174import com.unboundid.util.args.ControlArgument; 175import com.unboundid.util.args.DNArgument; 176import com.unboundid.util.args.FileArgument; 177import com.unboundid.util.args.FilterArgument; 178import com.unboundid.util.args.IntegerArgument; 179import com.unboundid.util.args.ScopeArgument; 180import com.unboundid.util.args.StringArgument; 181import com.unboundid.util.json.JSONBoolean; 182import com.unboundid.util.json.JSONNumber; 183import com.unboundid.util.json.JSONObject; 184import com.unboundid.util.json.JSONString; 185import com.unboundid.util.json.JSONValue; 186 187import static com.unboundid.ldap.sdk.unboundidds.tools.ToolMessages.*; 188 189 190 191/** 192 * This class provides an implementation of an LDAP command-line tool that may 193 * be used to issue searches to a directory server. Matching entries will be 194 * output in the LDAP data interchange format (LDIF), to standard output and/or 195 * to a specified file. This is a much more full-featured tool than the 196 * {@link com.unboundid.ldap.sdk.examples.LDAPSearch} tool, and includes a 197 * number of features only intended for use with Ping Identity, UnboundID, and 198 * Nokia/Alcatel-Lucent 8661 server products. 199 * <BR> 200 * <BLOCKQUOTE> 201 * <B>NOTE:</B> This class, and other classes within the 202 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 203 * supported for use against Ping Identity, UnboundID, and 204 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 205 * for proprietary functionality or for external specifications that are not 206 * considered stable or mature enough to be guaranteed to work in an 207 * interoperable way with other types of LDAP servers. 208 * </BLOCKQUOTE> 209 */ 210@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 211public final class LDAPSearch 212 extends LDAPCommandLineTool 213 implements UnsolicitedNotificationHandler 214{ 215 /** 216 * The column at which to wrap long lines. 217 */ 218 private static int WRAP_COLUMN = StaticUtils.TERMINAL_WIDTH_COLUMNS - 1; 219 220 221 222 // The set of arguments supported by this program. 223 @Nullable private BooleanArgument accountUsable = null; 224 @Nullable private BooleanArgument authorizationIdentity = null; 225 @Nullable private BooleanArgument compressOutput = null; 226 @Nullable private BooleanArgument continueOnError = null; 227 @Nullable private BooleanArgument countEntries = null; 228 @Nullable private BooleanArgument dontWrap = null; 229 @Nullable private BooleanArgument draftLDUPSubentries = null; 230 @Nullable private BooleanArgument dryRun = null; 231 @Nullable private BooleanArgument encryptOutput = null; 232 @Nullable private BooleanArgument followReferrals = null; 233 @Nullable private BooleanArgument generateAccessToken = null; 234 @Nullable private BooleanArgument getBackendSetID = null; 235 @Nullable private BooleanArgument getServerID = null; 236 @Nullable private BooleanArgument getRecentLoginHistory = null; 237 @Nullable private BooleanArgument hideRedactedValueCount = null; 238 @Nullable private BooleanArgument getUserResourceLimits = null; 239 @Nullable private BooleanArgument includeReplicationConflictEntries = null; 240 @Nullable private BooleanArgument joinRequireMatch = null; 241 @Nullable private BooleanArgument manageDsaIT = null; 242 @Nullable private BooleanArgument permitUnindexedSearch = null; 243 @Nullable private BooleanArgument realAttributesOnly = null; 244 @Nullable private BooleanArgument rejectUnindexedSearch = null; 245 @Nullable private BooleanArgument requireMatch = null; 246 @Nullable private BooleanArgument retryFailedOperations = null; 247 @Nullable private BooleanArgument separateOutputFilePerSearch = null; 248 @Nullable private BooleanArgument suppressBase64EncodedValueComments = null; 249 @Nullable private BooleanArgument teeResultsToStandardOut = null; 250 @Nullable private BooleanArgument useAdministrativeSession = null; 251 @Nullable private BooleanArgument useJSONFormattedRequestControls = null; 252 @Nullable private BooleanArgument usePasswordPolicyControl = null; 253 @Nullable private BooleanArgument terse = null; 254 @Nullable private BooleanArgument typesOnly = null; 255 @Nullable private BooleanArgument verbose = null; 256 @Nullable private BooleanArgument virtualAttributesOnly = null; 257 @Nullable private BooleanValueArgument rfc3672Subentries = null; 258 @Nullable private ControlArgument bindControl = null; 259 @Nullable private ControlArgument searchControl = null; 260 @Nullable private DNArgument baseDN = null; 261 @Nullable private DNArgument excludeBranch = null; 262 @Nullable private DNArgument moveSubtreeFrom = null; 263 @Nullable private DNArgument moveSubtreeTo = null; 264 @Nullable private DNArgument proxyV1As = null; 265 @Nullable private FileArgument encryptionPassphraseFile = null; 266 @Nullable private FileArgument filterFile = null; 267 @Nullable private FileArgument ldapURLFile = null; 268 @Nullable private FileArgument outputFile = null; 269 @Nullable private FilterArgument assertionFilter = null; 270 @Nullable private FilterArgument filter = null; 271 @Nullable private FilterArgument joinFilter = null; 272 @Nullable private FilterArgument matchedValuesFilter = null; 273 @Nullable private IntegerArgument joinSizeLimit = null; 274 @Nullable private IntegerArgument ratePerSecond = null; 275 @Nullable private IntegerArgument scrambleRandomSeed = null; 276 @Nullable private IntegerArgument simplePageSize = null; 277 @Nullable private IntegerArgument sizeLimit = null; 278 @Nullable private IntegerArgument timeLimitSeconds = null; 279 @Nullable private IntegerArgument wrapColumn = null; 280 @Nullable private ScopeArgument joinScope = null; 281 @Nullable private ScopeArgument scope = null; 282 @Nullable private StringArgument accessLogField = null; 283 @Nullable private StringArgument dereferencePolicy = null; 284 @Nullable private StringArgument excludeAttribute = null; 285 @Nullable private StringArgument getAuthorizationEntryAttribute = null; 286 @Nullable private StringArgument getEffectiveRightsAttribute = null; 287 @Nullable private StringArgument getEffectiveRightsAuthzID = null; 288 @Nullable private StringArgument includeSoftDeletedEntries = null; 289 @Nullable private StringArgument joinBaseDN = null; 290 @Nullable private StringArgument joinRequestedAttribute = null; 291 @Nullable private StringArgument joinRule = null; 292 @Nullable private StringArgument matchingEntryCountControl = null; 293 @Nullable private StringArgument operationPurpose = null; 294 @Nullable private StringArgument outputFormat = null; 295 @Nullable private StringArgument overrideSearchLimit = null; 296 @Nullable private StringArgument persistentSearch = null; 297 @Nullable private StringArgument proxyAs = null; 298 @Nullable private StringArgument redactAttribute = null; 299 @Nullable private StringArgument renameAttributeFrom = null; 300 @Nullable private StringArgument renameAttributeTo = null; 301 @Nullable private StringArgument requestedAttribute = null; 302 @Nullable private StringArgument routeToBackendSet = null; 303 @Nullable private StringArgument routeToServer = null; 304 @Nullable private StringArgument scrambleAttribute = null; 305 @Nullable private StringArgument scrambleJSONField = null; 306 @Nullable private StringArgument sortOrder = null; 307 @Nullable private StringArgument suppressOperationalAttributeUpdates = null; 308 @Nullable private StringArgument virtualListView = null; 309 310 // The argument parser used by this tool. 311 @Nullable private volatile ArgumentParser parser = null; 312 313 // Controls that should be sent to the server but need special validation. 314 @Nullable private volatile JoinRequestControl joinRequestControl = null; 315 @NotNull private final List<RouteToBackendSetRequestControl> 316 routeToBackendSetRequestControls = new ArrayList<>(10); 317 @Nullable private volatile MatchedValuesRequestControl 318 matchedValuesRequestControl = null; 319 @Nullable private volatile MatchingEntryCountRequestControl 320 matchingEntryCountRequestControl = null; 321 @Nullable private volatile OverrideSearchLimitsRequestControl 322 overrideSearchLimitsRequestControl = null; 323 @Nullable private volatile PersistentSearchRequestControl 324 persistentSearchRequestControl = null; 325 @Nullable private volatile ServerSideSortRequestControl sortRequestControl = 326 null; 327 @Nullable private volatile VirtualListViewRequestControl vlvRequestControl = 328 null; 329 330 // Other values decoded from arguments. 331 @Nullable private volatile DereferencePolicy derefPolicy = null; 332 333 // The print streams used for standard output and error. 334 @NotNull private final AtomicLong outputFileCounter = new AtomicLong(1); 335 @Nullable private volatile PrintStream errStream = null; 336 @Nullable private volatile PrintStream outStream = null; 337 338 // The LDAP result writer for this tool. 339 @NotNull private volatile LDAPResultWriter resultWriter; 340 341 // The list of entry transformations to apply. 342 @Nullable private volatile List<EntryTransformation> entryTransformations = 343 null; 344 345 // The encryption passphrase to use if the output is to be encrypted. 346 @Nullable private String encryptionPassphrase = null; 347 348 349 350 /** 351 * Runs this tool with the provided command-line arguments. It will use the 352 * JVM-default streams for standard input, output, and error. 353 * 354 * @param args The command-line arguments to provide to this program. 355 */ 356 public static void main(@NotNull final String... args) 357 { 358 final ResultCode resultCode = main(System.out, System.err, args); 359 if (resultCode != ResultCode.SUCCESS) 360 { 361 System.exit(Math.min(resultCode.intValue(), 255)); 362 } 363 } 364 365 366 367 /** 368 * Runs this tool with the provided streams and command-line arguments. 369 * 370 * @param out The output stream to use for standard output. If this is 371 * {@code null}, then standard output will be suppressed. 372 * @param err The output stream to use for standard error. If this is 373 * {@code null}, then standard error will be suppressed. 374 * @param args The command-line arguments provided to this program. 375 * 376 * @return The result code obtained when running the tool. Any result code 377 * other than {@link ResultCode#SUCCESS} indicates an error. 378 */ 379 @NotNull() 380 public static ResultCode main(@Nullable final OutputStream out, 381 @Nullable final OutputStream err, 382 @NotNull final String... args) 383 { 384 final LDAPSearch tool = new LDAPSearch(out, err); 385 return tool.runTool(args); 386 } 387 388 389 390 /** 391 * Creates a new instance of this tool with the provided streams. 392 * 393 * @param out The output stream to use for standard output. If this is 394 * {@code null}, then standard output will be suppressed. 395 * @param err The output stream to use for standard error. If this is 396 * {@code null}, then standard error will be suppressed. 397 */ 398 public LDAPSearch(@Nullable final OutputStream out, 399 @Nullable final OutputStream err) 400 { 401 super(out, err); 402 403 resultWriter = new LDIFLDAPResultWriter(getOut(), WRAP_COLUMN); 404 } 405 406 407 408 /** 409 * {@inheritDoc} 410 */ 411 @Override() 412 @NotNull() 413 public String getToolName() 414 { 415 return "ldapsearch"; 416 } 417 418 419 420 /** 421 * {@inheritDoc} 422 */ 423 @Override() 424 @NotNull() 425 public String getToolDescription() 426 { 427 return INFO_LDAPSEARCH_TOOL_DESCRIPTION.get(); 428 } 429 430 431 432 /** 433 * {@inheritDoc} 434 */ 435 @Override() 436 @NotNull() 437 public List<String> getAdditionalDescriptionParagraphs() 438 { 439 return Arrays.asList( 440 INFO_LDAPSEARCH_ADDITIONAL_DESCRIPTION_PARAGRAPH_1.get(), 441 INFO_LDAPSEARCH_ADDITIONAL_DESCRIPTION_PARAGRAPH_2.get()); 442 } 443 444 445 446 /** 447 * {@inheritDoc} 448 */ 449 @Override() 450 @NotNull() 451 public String getToolVersion() 452 { 453 return Version.NUMERIC_VERSION_STRING; 454 } 455 456 457 458 /** 459 * {@inheritDoc} 460 */ 461 @Override() 462 public int getMinTrailingArguments() 463 { 464 return 0; 465 } 466 467 468 469 /** 470 * {@inheritDoc} 471 */ 472 @Override() 473 public int getMaxTrailingArguments() 474 { 475 return -1; 476 } 477 478 479 480 /** 481 * {@inheritDoc} 482 */ 483 @Override() 484 @NotNull() 485 public String getTrailingArgumentsPlaceholder() 486 { 487 return INFO_LDAPSEARCH_TRAILING_ARGS_PLACEHOLDER.get(); 488 } 489 490 491 492 /** 493 * {@inheritDoc} 494 */ 495 @Override() 496 public boolean supportsInteractiveMode() 497 { 498 return true; 499 } 500 501 502 503 /** 504 * {@inheritDoc} 505 */ 506 @Override() 507 public boolean defaultsToInteractiveMode() 508 { 509 return true; 510 } 511 512 513 514 /** 515 * {@inheritDoc} 516 */ 517 @Override() 518 public boolean supportsPropertiesFile() 519 { 520 return true; 521 } 522 523 524 525 /** 526 * {@inheritDoc} 527 */ 528 @Override() 529 protected boolean supportsDebugLogging() 530 { 531 return true; 532 } 533 534 535 536 /** 537 * {@inheritDoc} 538 */ 539 @Override() 540 protected boolean defaultToPromptForBindPassword() 541 { 542 return true; 543 } 544 545 546 547 /** 548 * {@inheritDoc} 549 */ 550 @Override() 551 protected boolean includeAlternateLongIdentifiers() 552 { 553 return true; 554 } 555 556 557 558 /** 559 * {@inheritDoc} 560 */ 561 @Override() 562 protected boolean supportsSSLDebugging() 563 { 564 return true; 565 } 566 567 568 569 /** 570 * {@inheritDoc} 571 */ 572 @Override() 573 @NotNull() 574 protected Set<Character> getSuppressedShortIdentifiers() 575 { 576 return Collections.singleton('T'); 577 } 578 579 580 581 /** 582 * {@inheritDoc} 583 */ 584 @Override() 585 public void addNonLDAPArguments(@NotNull final ArgumentParser parser) 586 throws ArgumentException 587 { 588 this.parser = parser; 589 590 baseDN = new DNArgument('b', "baseDN", false, 1, null, 591 INFO_LDAPSEARCH_ARG_DESCRIPTION_BASE_DN.get()); 592 baseDN.addLongIdentifier("base-dn", true); 593 baseDN.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get()); 594 parser.addArgument(baseDN); 595 596 scope = new ScopeArgument('s', "scope", false, null, 597 INFO_LDAPSEARCH_ARG_DESCRIPTION_SCOPE.get(), SearchScope.SUB); 598 scope.addLongIdentifier("searchScope", true); 599 scope.addLongIdentifier("search-scope", true); 600 scope.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get()); 601 parser.addArgument(scope); 602 603 sizeLimit = new IntegerArgument('z', "sizeLimit", false, 1, null, 604 INFO_LDAPSEARCH_ARG_DESCRIPTION_SIZE_LIMIT.get(), 0, 605 Integer.MAX_VALUE, 0); 606 sizeLimit.addLongIdentifier("size-limit", true); 607 sizeLimit.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get()); 608 parser.addArgument(sizeLimit); 609 610 timeLimitSeconds = new IntegerArgument('l', "timeLimitSeconds", false, 1, 611 null, INFO_LDAPSEARCH_ARG_DESCRIPTION_TIME_LIMIT.get(), 0, 612 Integer.MAX_VALUE, 0); 613 timeLimitSeconds.addLongIdentifier("timeLimit", true); 614 timeLimitSeconds.addLongIdentifier("time-limit-seconds", true); 615 timeLimitSeconds.addLongIdentifier("time-limit", true); 616 timeLimitSeconds.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get()); 617 parser.addArgument(timeLimitSeconds); 618 619 final Set<String> derefAllowedValues = 620 StaticUtils.setOf("never", "always", "search", "find"); 621 dereferencePolicy = new StringArgument('a', "dereferencePolicy", false, 1, 622 "{never|always|search|find}", 623 INFO_LDAPSEARCH_ARG_DESCRIPTION_DEREFERENCE_POLICY.get(), 624 derefAllowedValues, "never"); 625 dereferencePolicy.addLongIdentifier("dereference-policy", true); 626 dereferencePolicy.setArgumentGroupName( 627 INFO_LDAPSEARCH_ARG_GROUP_OPS.get()); 628 parser.addArgument(dereferencePolicy); 629 630 typesOnly = new BooleanArgument('A', "typesOnly", 1, 631 INFO_LDAPSEARCH_ARG_DESCRIPTION_TYPES_ONLY.get()); 632 typesOnly.addLongIdentifier("types-only", true); 633 typesOnly.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get()); 634 parser.addArgument(typesOnly); 635 636 requestedAttribute = new StringArgument(null, "requestedAttribute", false, 637 0, INFO_PLACEHOLDER_ATTR.get(), 638 INFO_LDAPSEARCH_ARG_DESCRIPTION_REQUESTED_ATTR.get()); 639 requestedAttribute.addLongIdentifier("requested-attribute", true); 640 requestedAttribute.setArgumentGroupName( 641 INFO_LDAPSEARCH_ARG_GROUP_OPS.get()); 642 parser.addArgument(requestedAttribute); 643 644 filter = new FilterArgument(null, "filter", false, 0, 645 INFO_PLACEHOLDER_FILTER.get(), 646 INFO_LDAPSEARCH_ARG_DESCRIPTION_FILTER.get()); 647 filter.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get()); 648 parser.addArgument(filter); 649 650 filterFile = new FileArgument('f', "filterFile", false, 0, null, 651 INFO_LDAPSEARCH_ARG_DESCRIPTION_FILTER_FILE.get(), true, true, 652 true, false); 653 filterFile.addLongIdentifier("filename", true); 654 filterFile.addLongIdentifier("filter-file", true); 655 filterFile.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get()); 656 parser.addArgument(filterFile); 657 658 ldapURLFile = new FileArgument(null, "ldapURLFile", false, 0, null, 659 INFO_LDAPSEARCH_ARG_DESCRIPTION_LDAP_URL_FILE.get(), true, true, 660 true, false); 661 ldapURLFile.addLongIdentifier("ldap-url-file", true); 662 ldapURLFile.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get()); 663 parser.addArgument(ldapURLFile); 664 665 followReferrals = new BooleanArgument(null, "followReferrals", 1, 666 INFO_LDAPSEARCH_ARG_DESCRIPTION_FOLLOW_REFERRALS.get()); 667 followReferrals.addLongIdentifier("follow-referrals", true); 668 followReferrals.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get()); 669 parser.addArgument(followReferrals); 670 671 retryFailedOperations = new BooleanArgument(null, "retryFailedOperations", 672 1, INFO_LDAPSEARCH_ARG_DESCRIPTION_RETRY_FAILED_OPERATIONS.get()); 673 retryFailedOperations.addLongIdentifier("retry-failed-operations", true); 674 retryFailedOperations.setArgumentGroupName( 675 INFO_LDAPSEARCH_ARG_GROUP_OPS.get()); 676 parser.addArgument(retryFailedOperations); 677 678 continueOnError = new BooleanArgument('c', "continueOnError", 1, 679 INFO_LDAPSEARCH_ARG_DESCRIPTION_CONTINUE_ON_ERROR.get()); 680 continueOnError.addLongIdentifier("continue-on-error", true); 681 continueOnError.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get()); 682 parser.addArgument(continueOnError); 683 684 ratePerSecond = new IntegerArgument('r', "ratePerSecond", false, 1, 685 INFO_PLACEHOLDER_NUM.get(), 686 INFO_LDAPSEARCH_ARG_DESCRIPTION_RATE_PER_SECOND.get(), 1, 687 Integer.MAX_VALUE); 688 ratePerSecond.addLongIdentifier("rate-per-second", true); 689 ratePerSecond.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get()); 690 parser.addArgument(ratePerSecond); 691 692 useAdministrativeSession = new BooleanArgument(null, 693 "useAdministrativeSession", 1, 694 INFO_LDAPSEARCH_ARG_DESCRIPTION_USE_ADMIN_SESSION.get()); 695 useAdministrativeSession.addLongIdentifier("use-administrative-session", 696 true); 697 useAdministrativeSession.setArgumentGroupName( 698 INFO_LDAPSEARCH_ARG_GROUP_OPS.get()); 699 parser.addArgument(useAdministrativeSession); 700 701 dryRun = new BooleanArgument('n', "dryRun", 1, 702 INFO_LDAPSEARCH_ARG_DESCRIPTION_DRY_RUN.get()); 703 dryRun.addLongIdentifier("dry-run", true); 704 dryRun.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get()); 705 parser.addArgument(dryRun); 706 707 wrapColumn = new IntegerArgument(null, "wrapColumn", false, 1, null, 708 INFO_LDAPSEARCH_ARG_DESCRIPTION_WRAP_COLUMN.get(), 0, 709 Integer.MAX_VALUE); 710 wrapColumn.addLongIdentifier("wrap-column", true); 711 wrapColumn.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_DATA.get()); 712 parser.addArgument(wrapColumn); 713 714 dontWrap = new BooleanArgument('T', "dontWrap", 1, 715 INFO_LDAPSEARCH_ARG_DESCRIPTION_DONT_WRAP.get()); 716 dontWrap.addLongIdentifier("doNotWrap", true); 717 dontWrap.addLongIdentifier("dont-wrap", true); 718 dontWrap.addLongIdentifier("do-not-wrap", true); 719 dontWrap.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_DATA.get()); 720 parser.addArgument(dontWrap); 721 722 suppressBase64EncodedValueComments = new BooleanArgument(null, 723 "suppressBase64EncodedValueComments", 1, 724 INFO_LDAPSEARCH_ARG_DESCRIPTION_SUPPRESS_BASE64_COMMENTS.get()); 725 suppressBase64EncodedValueComments.addLongIdentifier( 726 "suppress-base64-encoded-value-comments", true); 727 suppressBase64EncodedValueComments.setArgumentGroupName( 728 INFO_LDAPSEARCH_ARG_GROUP_DATA.get()); 729 parser.addArgument(suppressBase64EncodedValueComments); 730 731 countEntries = new BooleanArgument(null, "countEntries", 1, 732 INFO_LDAPSEARCH_ARG_DESCRIPTION_COUNT_ENTRIES.get()); 733 countEntries.addLongIdentifier("count-entries", true); 734 countEntries.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_OPS.get()); 735 countEntries.setHidden(true); 736 parser.addArgument(countEntries); 737 738 outputFile = new FileArgument(null, "outputFile", false, 1, null, 739 INFO_LDAPSEARCH_ARG_DESCRIPTION_OUTPUT_FILE.get(), false, true, true, 740 false); 741 outputFile.addLongIdentifier("output-file", true); 742 outputFile.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_DATA.get()); 743 parser.addArgument(outputFile); 744 745 compressOutput = new BooleanArgument(null, "compressOutput", 1, 746 INFO_LDAPSEARCH_ARG_DESCRIPTION_COMPRESS_OUTPUT.get()); 747 compressOutput.addLongIdentifier("compress-output", true); 748 compressOutput.addLongIdentifier("compress", true); 749 compressOutput.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_DATA.get()); 750 parser.addArgument(compressOutput); 751 752 encryptOutput = new BooleanArgument(null, "encryptOutput", 1, 753 INFO_LDAPSEARCH_ARG_DESCRIPTION_ENCRYPT_OUTPUT.get()); 754 encryptOutput.addLongIdentifier("encrypt-output", true); 755 encryptOutput.addLongIdentifier("encrypt", true); 756 encryptOutput.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_DATA.get()); 757 parser.addArgument(encryptOutput); 758 759 encryptionPassphraseFile = new FileArgument(null, 760 "encryptionPassphraseFile", false, 1, null, 761 INFO_LDAPSEARCH_ARG_DESCRIPTION_ENCRYPTION_PW_FILE.get(), true, true, 762 true, false); 763 encryptionPassphraseFile.addLongIdentifier("encryption-passphrase-file", 764 true); 765 encryptionPassphraseFile.addLongIdentifier("encryptionPasswordFile", true); 766 encryptionPassphraseFile.addLongIdentifier("encryption-password-file", 767 true); 768 encryptionPassphraseFile.setArgumentGroupName( 769 INFO_LDAPSEARCH_ARG_GROUP_DATA.get()); 770 parser.addArgument(encryptionPassphraseFile); 771 772 separateOutputFilePerSearch = new BooleanArgument(null, 773 "separateOutputFilePerSearch", 1, 774 INFO_LDAPSEARCH_ARG_DESCRIPTION_SEPARATE_OUTPUT_FILES.get()); 775 separateOutputFilePerSearch.addLongIdentifier( 776 "separate-output-file-per-search", true); 777 separateOutputFilePerSearch.setArgumentGroupName( 778 INFO_LDAPSEARCH_ARG_GROUP_DATA.get()); 779 parser.addArgument(separateOutputFilePerSearch); 780 781 teeResultsToStandardOut = new BooleanArgument(null, 782 "teeResultsToStandardOut", 1, 783 INFO_LDAPSEARCH_ARG_DESCRIPTION_TEE.get("outputFile")); 784 teeResultsToStandardOut.addLongIdentifier( 785 "tee-results-to-standard-out", true); 786 teeResultsToStandardOut.setArgumentGroupName( 787 INFO_LDAPSEARCH_ARG_GROUP_DATA.get()); 788 parser.addArgument(teeResultsToStandardOut); 789 790 final Set<String> outputFormatAllowedValues = StaticUtils.setOf("ldif", 791 "json", "csv", "multi-valued-csv", "tab-delimited", 792 "multi-valued-tab-delimited", "dns-only", "values-only"); 793 outputFormat = new StringArgument(null, "outputFormat", false, 1, 794 "{ldif|json|csv|multi-valued-csv|tab-delimited|" + 795 "multi-valued-tab-delimited|dns-only|values-only}", 796 INFO_LDAPSEARCH_ARG_DESCRIPTION_OUTPUT_FORMAT.get( 797 requestedAttribute.getIdentifierString(), 798 ldapURLFile.getIdentifierString()), 799 outputFormatAllowedValues, "ldif"); 800 outputFormat.addLongIdentifier("output-format", true); 801 outputFormat.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_DATA.get()); 802 parser.addArgument(outputFormat); 803 804 requireMatch = new BooleanArgument(null, "requireMatch", 1, 805 INFO_LDAPSEARCH_ARG_DESCRIPTION_REQUIRE_MATCH.get( 806 getToolName(), 807 String.valueOf(ResultCode.NO_RESULTS_RETURNED))); 808 requireMatch.addLongIdentifier("require-match", true); 809 requireMatch.addLongIdentifier("requireMatchingEntry", true); 810 requireMatch.addLongIdentifier("require-matching-entry", true); 811 requireMatch.addLongIdentifier("requireMatchingEntries", true); 812 requireMatch.addLongIdentifier("require-matching-entries", true); 813 requireMatch.addLongIdentifier("requireEntry", true); 814 requireMatch.addLongIdentifier("require-entry", true); 815 requireMatch.addLongIdentifier("requireEntries", true); 816 requireMatch.addLongIdentifier("require-entries", true); 817 requireMatch.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_DATA.get()); 818 parser.addArgument(requireMatch); 819 820 terse = new BooleanArgument(null, "terse", 1, 821 INFO_LDAPSEARCH_ARG_DESCRIPTION_TERSE.get()); 822 terse.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_DATA.get()); 823 parser.addArgument(terse); 824 825 verbose = new BooleanArgument('v', "verbose", 1, 826 INFO_LDAPSEARCH_ARG_DESCRIPTION_VERBOSE.get()); 827 verbose.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_DATA.get()); 828 parser.addArgument(verbose); 829 830 bindControl = new ControlArgument(null, "bindControl", false, 0, null, 831 INFO_LDAPSEARCH_ARG_DESCRIPTION_BIND_CONTROL.get()); 832 bindControl.addLongIdentifier("bind-control", true); 833 bindControl.setArgumentGroupName( 834 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 835 parser.addArgument(bindControl); 836 837 searchControl = new ControlArgument('J', "control", false, 0, null, 838 INFO_LDAPSEARCH_ARG_DESCRIPTION_SEARCH_CONTROL.get()); 839 searchControl.addLongIdentifier("searchControl", true); 840 searchControl.addLongIdentifier("search-control", true); 841 searchControl.setArgumentGroupName( 842 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 843 parser.addArgument(searchControl); 844 845 accessLogField = new StringArgument(null, "accessLogField", false, 0, 846 INFO_LDAPSEARCH_ARG_PLACEHOLDER_NAME_VALUE.get(), 847 INFO_LDAPSEARCH_ARG_DESCRIPTION_ACCESS_LOG_FIELD.get()); 848 accessLogField.addLongIdentifier("access-log-field", true); 849 accessLogField.setArgumentGroupName( 850 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 851 parser.addArgument(accessLogField); 852 853 accountUsable = new BooleanArgument(null, "accountUsable", 1, 854 INFO_LDAPSEARCH_ARG_DESCRIPTION_ACCOUNT_USABLE.get()); 855 accountUsable.addLongIdentifier("account-usable", true); 856 accountUsable.setArgumentGroupName( 857 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 858 parser.addArgument(accountUsable); 859 860 authorizationIdentity = new BooleanArgument('E', "authorizationIdentity", 861 1, INFO_LDAPSEARCH_ARG_DESCRIPTION_AUTHZ_IDENTITY.get()); 862 authorizationIdentity.addLongIdentifier("reportAuthzID", true); 863 authorizationIdentity.addLongIdentifier("authorization-identity", true); 864 authorizationIdentity.addLongIdentifier("report-authzid", true); 865 authorizationIdentity.setArgumentGroupName( 866 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 867 parser.addArgument(authorizationIdentity); 868 869 assertionFilter = new FilterArgument(null, "assertionFilter", false, 1, 870 INFO_PLACEHOLDER_FILTER.get(), 871 INFO_LDAPSEARCH_ARG_DESCRIPTION_ASSERTION_FILTER.get()); 872 assertionFilter.addLongIdentifier("assertion-filter", true); 873 assertionFilter.setArgumentGroupName( 874 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 875 parser.addArgument(assertionFilter); 876 877 excludeBranch = new DNArgument(null, "excludeBranch", false, 0, null, 878 INFO_LDAPSEARCH_ARG_DESCRIPTION_EXCLUDE_BRANCH.get()); 879 excludeBranch.addLongIdentifier("exclude-branch", true); 880 excludeBranch.setArgumentGroupName( 881 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 882 parser.addArgument(excludeBranch); 883 884 generateAccessToken = new BooleanArgument(null, "generateAccessToken", 1, 885 INFO_LDAPSEARCH_ARG_DESCRIPTION_GENERATE_ACCESS_TOKEN.get()); 886 generateAccessToken.addLongIdentifier("generate-access-token", true); 887 generateAccessToken.addLongIdentifier("requestAccessToken", true); 888 generateAccessToken.addLongIdentifier("request-access-token", true); 889 generateAccessToken.setArgumentGroupName( 890 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 891 parser.addArgument(generateAccessToken); 892 893 getAuthorizationEntryAttribute = new StringArgument(null, 894 "getAuthorizationEntryAttribute", false, 0, 895 INFO_PLACEHOLDER_ATTR.get(), 896 INFO_LDAPSEARCH_ARG_DESCRIPTION_GET_AUTHZ_ENTRY_ATTR.get()); 897 getAuthorizationEntryAttribute.addLongIdentifier( 898 "get-authorization-entry-attribute", true); 899 getAuthorizationEntryAttribute.setArgumentGroupName( 900 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 901 parser.addArgument(getAuthorizationEntryAttribute); 902 903 getBackendSetID = new BooleanArgument(null, "getBackendSetID", 904 1, INFO_LDAPSEARCH_ARG_DESCRIPTION_GET_BACKEND_SET_ID.get()); 905 getBackendSetID.addLongIdentifier("get-backend-set-id", true); 906 getBackendSetID.setArgumentGroupName( 907 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 908 parser.addArgument(getBackendSetID); 909 910 getEffectiveRightsAuthzID = new StringArgument('g', 911 "getEffectiveRightsAuthzID", false, 1, 912 INFO_PLACEHOLDER_AUTHZID.get(), 913 INFO_LDAPSEARCH_ARG_DESCRIPTION_GET_EFFECTIVE_RIGHTS_AUTHZID.get( 914 "getEffectiveRightsAttribute")); 915 getEffectiveRightsAuthzID.addLongIdentifier( 916 "get-effective-rights-authzid", true); 917 getEffectiveRightsAuthzID.setArgumentGroupName( 918 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 919 parser.addArgument(getEffectiveRightsAuthzID); 920 921 getEffectiveRightsAttribute = new StringArgument('e', 922 "getEffectiveRightsAttribute", false, 0, 923 INFO_PLACEHOLDER_ATTR.get(), 924 INFO_LDAPSEARCH_ARG_DESCRIPTION_GET_EFFECTIVE_RIGHTS_ATTR.get()); 925 getEffectiveRightsAttribute.addLongIdentifier( 926 "get-effective-rights-attribute", true); 927 getEffectiveRightsAttribute.setArgumentGroupName( 928 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 929 parser.addArgument(getEffectiveRightsAttribute); 930 931 getRecentLoginHistory = new BooleanArgument(null, "getRecentLoginHistory", 932 1, INFO_LDAPSEARCH_ARG_DESCRIPTION_GET_RECENT_LOGIN_HISTORY.get()); 933 getRecentLoginHistory.addLongIdentifier("get-recent-login-history", true); 934 getRecentLoginHistory.setArgumentGroupName( 935 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 936 parser.addArgument(getRecentLoginHistory); 937 938 getServerID = new BooleanArgument(null, "getServerID", 939 1, INFO_LDAPSEARCH_ARG_DESCRIPTION_GET_SERVER_ID.get()); 940 getServerID.addLongIdentifier("get-server-id", true); 941 getServerID.setArgumentGroupName( 942 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 943 parser.addArgument(getServerID); 944 945 getUserResourceLimits = new BooleanArgument(null, "getUserResourceLimits", 946 1, INFO_LDAPSEARCH_ARG_DESCRIPTION_GET_USER_RESOURCE_LIMITS.get()); 947 getUserResourceLimits.addLongIdentifier("get-user-resource-limits", true); 948 getUserResourceLimits.setArgumentGroupName( 949 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 950 parser.addArgument(getUserResourceLimits); 951 952 includeReplicationConflictEntries = new BooleanArgument(null, 953 "includeReplicationConflictEntries", 1, 954 INFO_LDAPSEARCH_ARG_DESCRIPTION_INCLUDE_REPL_CONFLICTS.get()); 955 includeReplicationConflictEntries.addLongIdentifier( 956 "include-replication-conflict-entries", true); 957 includeReplicationConflictEntries.setArgumentGroupName( 958 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 959 parser.addArgument(includeReplicationConflictEntries); 960 961 final Set<String> softDeleteAllowedValues = StaticUtils.setOf( 962 "with-non-deleted-entries", "without-non-deleted-entries", 963 "deleted-entries-in-undeleted-form"); 964 includeSoftDeletedEntries = new StringArgument(null, 965 "includeSoftDeletedEntries", false, 1, 966 "{with-non-deleted-entries|without-non-deleted-entries|" + 967 "deleted-entries-in-undeleted-form}", 968 INFO_LDAPSEARCH_ARG_DESCRIPTION_INCLUDE_SOFT_DELETED.get(), 969 softDeleteAllowedValues); 970 includeSoftDeletedEntries.addLongIdentifier( 971 "include-soft-deleted-entries", true); 972 includeSoftDeletedEntries.setArgumentGroupName( 973 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 974 parser.addArgument(includeSoftDeletedEntries); 975 976 draftLDUPSubentries = new BooleanArgument(null, "draftLDUPSubentries", 1, 977 INFO_LDAPSEARCH_ARG_DESCRIPTION_INCLUDE_DRAFT_LDUP_SUBENTRIES.get()); 978 draftLDUPSubentries.addLongIdentifier("draftIETFLDUPSubentries", true); 979 draftLDUPSubentries.addLongIdentifier("includeSubentries", true); 980 draftLDUPSubentries.addLongIdentifier("includeLDAPSubentries", true); 981 draftLDUPSubentries.addLongIdentifier("draft-ldup-subentries", true); 982 draftLDUPSubentries.addLongIdentifier("draft-ietf-ldup-subentries", true); 983 draftLDUPSubentries.addLongIdentifier("include-subentries", true); 984 draftLDUPSubentries.addLongIdentifier("include-ldap-subentries", true); 985 draftLDUPSubentries.setArgumentGroupName( 986 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 987 parser.addArgument(draftLDUPSubentries); 988 989 rfc3672Subentries = new BooleanValueArgument(null, "rfc3672Subentries", 990 false, 991 INFO_LDAPSEARCH_ARG_PLACEHOLDER_INCLUDE_RFC_3672_SUBENTRIES.get(), 992 INFO_LDAPSEARCH_ARG_DESCRIPTION_INCLUDE_RFC_3672_SUBENTRIES.get()); 993 rfc3672Subentries.addLongIdentifier("rfc-3672-subentries", true); 994 rfc3672Subentries.addLongIdentifier("rfc3672-subentries", true); 995 rfc3672Subentries.setArgumentGroupName( 996 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 997 parser.addArgument(rfc3672Subentries); 998 999 joinRule = new StringArgument(null, "joinRule", false, 1, 1000 "{dn:sourceAttr|reverse-dn:targetAttr|equals:sourceAttr:targetAttr|" + 1001 "contains:sourceAttr:targetAttr }", 1002 INFO_LDAPSEARCH_ARG_DESCRIPTION_JOIN_RULE.get()); 1003 joinRule.addLongIdentifier("join-rule", true); 1004 joinRule.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 1005 parser.addArgument(joinRule); 1006 1007 joinBaseDN = new StringArgument(null, "joinBaseDN", false, 1, 1008 "{search-base|source-entry-dn|{dn}}", 1009 INFO_LDAPSEARCH_ARG_DESCRIPTION_JOIN_BASE_DN.get()); 1010 joinBaseDN.addLongIdentifier("join-base-dn", true); 1011 joinBaseDN.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 1012 parser.addArgument(joinBaseDN); 1013 1014 joinScope = new ScopeArgument(null, "joinScope", false, null, 1015 INFO_LDAPSEARCH_ARG_DESCRIPTION_JOIN_SCOPE.get()); 1016 joinScope.addLongIdentifier("join-scope", true); 1017 joinScope.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 1018 parser.addArgument(joinScope); 1019 1020 joinSizeLimit = new IntegerArgument(null, "joinSizeLimit", false, 1, 1021 INFO_PLACEHOLDER_NUM.get(), 1022 INFO_LDAPSEARCH_ARG_DESCRIPTION_JOIN_SIZE_LIMIT.get(), 0, 1023 Integer.MAX_VALUE); 1024 joinSizeLimit.addLongIdentifier("join-size-limit", true); 1025 joinSizeLimit.setArgumentGroupName( 1026 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 1027 parser.addArgument(joinSizeLimit); 1028 1029 joinFilter = new FilterArgument(null, "joinFilter", false, 1, null, 1030 INFO_LDAPSEARCH_ARG_DESCRIPTION_JOIN_FILTER.get()); 1031 joinFilter.addLongIdentifier("join-filter", true); 1032 joinFilter.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 1033 parser.addArgument(joinFilter); 1034 1035 joinRequestedAttribute = new StringArgument(null, "joinRequestedAttribute", 1036 false, 0, INFO_PLACEHOLDER_ATTR.get(), 1037 INFO_LDAPSEARCH_ARG_DESCRIPTION_JOIN_ATTR.get()); 1038 joinRequestedAttribute.addLongIdentifier("join-requested-attribute", true); 1039 joinRequestedAttribute.setArgumentGroupName( 1040 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 1041 parser.addArgument(joinRequestedAttribute); 1042 1043 joinRequireMatch = new BooleanArgument(null, "joinRequireMatch", 1, 1044 INFO_LDAPSEARCH_ARG_DESCRIPTION_JOIN_REQUIRE_MATCH.get()); 1045 joinRequireMatch.addLongIdentifier("join-require-match", true); 1046 joinRequireMatch.setArgumentGroupName( 1047 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 1048 parser.addArgument(joinRequireMatch); 1049 1050 manageDsaIT = new BooleanArgument(null, "manageDsaIT", 1, 1051 INFO_LDAPSEARCH_ARG_DESCRIPTION_MANAGE_DSA_IT.get()); 1052 manageDsaIT.addLongIdentifier("manage-dsa-it", true); 1053 manageDsaIT.setArgumentGroupName( 1054 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 1055 parser.addArgument(manageDsaIT); 1056 1057 matchedValuesFilter = new FilterArgument(null, "matchedValuesFilter", 1058 false, 0, INFO_PLACEHOLDER_FILTER.get(), 1059 INFO_LDAPSEARCH_ARG_DESCRIPTION_MATCHED_VALUES_FILTER.get()); 1060 matchedValuesFilter.addLongIdentifier("matched-values-filter", true); 1061 matchedValuesFilter.setArgumentGroupName( 1062 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 1063 parser.addArgument(matchedValuesFilter); 1064 1065 matchingEntryCountControl = new StringArgument(null, 1066 "matchingEntryCountControl", false, 1, 1067 "{examineCount=NNN[:alwaysExamine][:allowUnindexed]" + 1068 "[:skipResolvingExplodedIndexes]" + 1069 "[:fastShortCircuitThreshold=NNN]" + 1070 "[:slowShortCircuitThreshold=NNN][:extendedResponseData]" + 1071 "[:debug]}", 1072 INFO_LDAPSEARCH_ARG_DESCRIPTION_MATCHING_ENTRY_COUNT_CONTROL.get()); 1073 matchingEntryCountControl.addLongIdentifier("matchingEntryCount", true); 1074 matchingEntryCountControl.addLongIdentifier( 1075 "matching-entry-count-control", true); 1076 matchingEntryCountControl.addLongIdentifier("matching-entry-count", true); 1077 matchingEntryCountControl.setArgumentGroupName( 1078 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 1079 parser.addArgument(matchingEntryCountControl); 1080 1081 operationPurpose = new StringArgument(null, "operationPurpose", false, 1, 1082 INFO_PLACEHOLDER_PURPOSE.get(), 1083 INFO_LDAPSEARCH_ARG_DESCRIPTION_OPERATION_PURPOSE.get()); 1084 operationPurpose.addLongIdentifier("operation-purpose", true); 1085 operationPurpose.setArgumentGroupName( 1086 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 1087 parser.addArgument(operationPurpose); 1088 1089 overrideSearchLimit = new StringArgument(null, "overrideSearchLimit", 1090 false, 0, INFO_LDAPSEARCH_NAME_VALUE_PLACEHOLDER.get(), 1091 INFO_LDAPSEARCH_ARG_DESCRIPTION_OVERRIDE_SEARCH_LIMIT.get()); 1092 overrideSearchLimit.addLongIdentifier("overrideSearchLimits", true); 1093 overrideSearchLimit.addLongIdentifier("override-search-limit", true); 1094 overrideSearchLimit.addLongIdentifier("override-search-limits", true); 1095 overrideSearchLimit.setArgumentGroupName( 1096 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 1097 parser.addArgument(overrideSearchLimit); 1098 1099 persistentSearch = new StringArgument('C', "persistentSearch", false, 1, 1100 "ps[:changetype[:changesonly[:entrychgcontrols]]]", 1101 INFO_LDAPSEARCH_ARG_DESCRIPTION_PERSISTENT_SEARCH.get()); 1102 persistentSearch.addLongIdentifier("persistent-search", true); 1103 persistentSearch.setArgumentGroupName( 1104 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 1105 parser.addArgument(persistentSearch); 1106 1107 permitUnindexedSearch = new BooleanArgument(null, "permitUnindexedSearch", 1108 1, INFO_LDAPSEARCH_ARG_DESCRIPTION_PERMIT_UNINDEXED_SEARCH.get()); 1109 permitUnindexedSearch.addLongIdentifier("permitUnindexedSearches", true); 1110 permitUnindexedSearch.addLongIdentifier("permitUnindexed", true); 1111 permitUnindexedSearch.addLongIdentifier("permitIfUnindexed", true); 1112 permitUnindexedSearch.addLongIdentifier("permit-unindexed-search", true); 1113 permitUnindexedSearch.addLongIdentifier("permit-unindexed-searches", true); 1114 permitUnindexedSearch.addLongIdentifier("permit-unindexed", true); 1115 permitUnindexedSearch.addLongIdentifier("permit-if-unindexed", true); 1116 permitUnindexedSearch.setArgumentGroupName( 1117 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 1118 parser.addArgument(permitUnindexedSearch); 1119 1120 proxyAs = new StringArgument('Y', "proxyAs", false, 1, 1121 INFO_PLACEHOLDER_AUTHZID.get(), 1122 INFO_LDAPSEARCH_ARG_DESCRIPTION_PROXY_AS.get()); 1123 proxyAs.addLongIdentifier("proxy-as", true); 1124 proxyAs.addLongIdentifier("proxyV2As", true); 1125 proxyAs.addLongIdentifier("proxy-v2-as", true); 1126 proxyAs.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 1127 parser.addArgument(proxyAs); 1128 1129 proxyV1As = new DNArgument(null, "proxyV1As", false, 1, null, 1130 INFO_LDAPSEARCH_ARG_DESCRIPTION_PROXY_V1_AS.get()); 1131 proxyV1As.addLongIdentifier("proxy-v1-as", true); 1132 proxyV1As.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 1133 parser.addArgument(proxyV1As); 1134 1135 rejectUnindexedSearch = new BooleanArgument(null, "rejectUnindexedSearch", 1136 1, INFO_LDAPSEARCH_ARG_DESCRIPTION_REJECT_UNINDEXED_SEARCH.get()); 1137 rejectUnindexedSearch.addLongIdentifier("rejectUnindexedSearches", true); 1138 rejectUnindexedSearch.addLongIdentifier("rejectUnindexed", true); 1139 rejectUnindexedSearch.addLongIdentifier("rejectIfUnindexed", true); 1140 rejectUnindexedSearch.addLongIdentifier("reject-unindexed-search", true); 1141 rejectUnindexedSearch.addLongIdentifier("reject-unindexed-searches", true); 1142 rejectUnindexedSearch.addLongIdentifier("reject-unindexed", true); 1143 rejectUnindexedSearch.addLongIdentifier("reject-if-unindexed", true); 1144 rejectUnindexedSearch.setArgumentGroupName( 1145 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 1146 parser.addArgument(rejectUnindexedSearch); 1147 1148 routeToBackendSet = new StringArgument(null, "routeToBackendSet", 1149 false, 0, 1150 INFO_LDAPSEARCH_ARG_PLACEHOLDER_ROUTE_TO_BACKEND_SET.get(), 1151 INFO_LDAPSEARCH_ARG_DESCRIPTION_ROUTE_TO_BACKEND_SET.get()); 1152 routeToBackendSet.addLongIdentifier("route-to-backend-set", true); 1153 routeToBackendSet.setArgumentGroupName( 1154 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 1155 parser.addArgument(routeToBackendSet); 1156 1157 routeToServer = new StringArgument(null, "routeToServer", false, 1, 1158 INFO_LDAPSEARCH_ARG_PLACEHOLDER_ROUTE_TO_SERVER.get(), 1159 INFO_LDAPSEARCH_ARG_DESCRIPTION_ROUTE_TO_SERVER.get()); 1160 routeToServer.addLongIdentifier("route-to-server", true); 1161 routeToServer.setArgumentGroupName( 1162 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 1163 parser.addArgument(routeToServer); 1164 1165 final Set<String> suppressOperationalAttributeUpdatesAllowedValues = 1166 StaticUtils.setOf("last-access-time", "last-login-time", 1167 "last-login-ip", "lastmod"); 1168 suppressOperationalAttributeUpdates = new StringArgument(null, 1169 "suppressOperationalAttributeUpdates", false, -1, 1170 INFO_PLACEHOLDER_ATTR.get(), 1171 INFO_LDAPSEARCH_ARG_DESCRIPTION_SUPPRESS_OP_ATTR_UPDATES.get(), 1172 suppressOperationalAttributeUpdatesAllowedValues); 1173 suppressOperationalAttributeUpdates.addLongIdentifier( 1174 "suppress-operational-attribute-updates", true); 1175 suppressOperationalAttributeUpdates.setArgumentGroupName( 1176 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 1177 parser.addArgument(suppressOperationalAttributeUpdates); 1178 1179 usePasswordPolicyControl = new BooleanArgument(null, 1180 "usePasswordPolicyControl", 1, 1181 INFO_LDAPSEARCH_ARG_DESCRIPTION_PASSWORD_POLICY.get()); 1182 usePasswordPolicyControl.addLongIdentifier("use-password-policy-control", 1183 true); 1184 usePasswordPolicyControl.setArgumentGroupName( 1185 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 1186 parser.addArgument(usePasswordPolicyControl); 1187 1188 realAttributesOnly = new BooleanArgument(null, "realAttributesOnly", 1, 1189 INFO_LDAPSEARCH_ARG_DESCRIPTION_REAL_ATTRS_ONLY.get()); 1190 realAttributesOnly.addLongIdentifier("real-attributes-only", true); 1191 realAttributesOnly.setArgumentGroupName( 1192 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 1193 parser.addArgument(realAttributesOnly); 1194 1195 sortOrder = new StringArgument('S', "sortOrder", false, 1, null, 1196 INFO_LDAPSEARCH_ARG_DESCRIPTION_SORT_ORDER.get()); 1197 sortOrder.addLongIdentifier("sort-order", true); 1198 sortOrder.setArgumentGroupName(INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 1199 parser.addArgument(sortOrder); 1200 1201 simplePageSize = new IntegerArgument(null, "simplePageSize", false, 1, 1202 null, INFO_LDAPSEARCH_ARG_DESCRIPTION_PAGE_SIZE.get(), 1, 1203 Integer.MAX_VALUE); 1204 simplePageSize.addLongIdentifier("simple-page-size", true); 1205 simplePageSize.setArgumentGroupName( 1206 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 1207 parser.addArgument(simplePageSize); 1208 1209 virtualAttributesOnly = new BooleanArgument(null, 1210 "virtualAttributesOnly", 1, 1211 INFO_LDAPSEARCH_ARG_DESCRIPTION_VIRTUAL_ATTRS_ONLY.get()); 1212 virtualAttributesOnly.addLongIdentifier("virtual-attributes-only", true); 1213 virtualAttributesOnly.setArgumentGroupName( 1214 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 1215 parser.addArgument(virtualAttributesOnly); 1216 1217 virtualListView = new StringArgument('G', "virtualListView", false, 1, 1218 "{before:after:index:count | before:after:value}", 1219 INFO_LDAPSEARCH_ARG_DESCRIPTION_VLV.get("sortOrder")); 1220 virtualListView.addLongIdentifier("vlv", true); 1221 virtualListView.addLongIdentifier("virtual-list-view", true); 1222 virtualListView.setArgumentGroupName( 1223 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 1224 parser.addArgument(virtualListView); 1225 1226 useJSONFormattedRequestControls = new BooleanArgument(null, 1227 "useJSONFormattedRequestControls", 1, 1228 INFO_LDAPSEARCH_ARG_DESCRIPTION_USE_JSON_FORMATTED_CONTROLS.get()); 1229 useJSONFormattedRequestControls.addLongIdentifier( 1230 "use-json-formatted-request-controls", true); 1231 useJSONFormattedRequestControls.addLongIdentifier( 1232 "useJSONFormattedControls", true); 1233 useJSONFormattedRequestControls.addLongIdentifier( 1234 "use-json-formatted-controls", true); 1235 useJSONFormattedRequestControls.setArgumentGroupName( 1236 INFO_LDAPSEARCH_ARG_GROUP_CONTROLS.get()); 1237 parser.addArgument(useJSONFormattedRequestControls); 1238 1239 excludeAttribute = new StringArgument(null, "excludeAttribute", false, 0, 1240 INFO_PLACEHOLDER_ATTR.get(), 1241 INFO_LDAPSEARCH_ARG_DESCRIPTION_EXCLUDE_ATTRIBUTE.get()); 1242 excludeAttribute.addLongIdentifier("exclude-attribute", true); 1243 excludeAttribute.setArgumentGroupName( 1244 INFO_LDAPSEARCH_ARG_GROUP_TRANSFORMATIONS.get()); 1245 parser.addArgument(excludeAttribute); 1246 1247 redactAttribute = new StringArgument(null, "redactAttribute", false, 0, 1248 INFO_PLACEHOLDER_ATTR.get(), 1249 INFO_LDAPSEARCH_ARG_DESCRIPTION_REDACT_ATTRIBUTE.get()); 1250 redactAttribute.addLongIdentifier("redact-attribute", true); 1251 redactAttribute.setArgumentGroupName( 1252 INFO_LDAPSEARCH_ARG_GROUP_TRANSFORMATIONS.get()); 1253 parser.addArgument(redactAttribute); 1254 1255 hideRedactedValueCount = new BooleanArgument(null, "hideRedactedValueCount", 1256 1, INFO_LDAPSEARCH_ARG_DESCRIPTION_HIDE_REDACTED_VALUE_COUNT.get()); 1257 hideRedactedValueCount.addLongIdentifier("hide-redacted-value-count", true); 1258 hideRedactedValueCount.setArgumentGroupName( 1259 INFO_LDAPSEARCH_ARG_GROUP_TRANSFORMATIONS.get()); 1260 parser.addArgument(hideRedactedValueCount); 1261 1262 scrambleAttribute = new StringArgument(null, "scrambleAttribute", false, 0, 1263 INFO_PLACEHOLDER_ATTR.get(), 1264 INFO_LDAPSEARCH_ARG_DESCRIPTION_SCRAMBLE_ATTRIBUTE.get()); 1265 scrambleAttribute.addLongIdentifier("scramble-attribute", true); 1266 scrambleAttribute.setArgumentGroupName( 1267 INFO_LDAPSEARCH_ARG_GROUP_TRANSFORMATIONS.get()); 1268 parser.addArgument(scrambleAttribute); 1269 1270 scrambleJSONField = new StringArgument(null, "scrambleJSONField", false, 0, 1271 INFO_PLACEHOLDER_FIELD_NAME.get(), 1272 INFO_LDAPSEARCH_ARG_DESCRIPTION_SCRAMBLE_JSON_FIELD.get()); 1273 scrambleJSONField.addLongIdentifier("scramble-json-field", true); 1274 scrambleJSONField.setArgumentGroupName( 1275 INFO_LDAPSEARCH_ARG_GROUP_TRANSFORMATIONS.get()); 1276 parser.addArgument(scrambleJSONField); 1277 1278 scrambleRandomSeed = new IntegerArgument(null, "scrambleRandomSeed", false, 1279 1, null, INFO_LDAPSEARCH_ARG_DESCRIPTION_SCRAMBLE_RANDOM_SEED.get()); 1280 scrambleRandomSeed.addLongIdentifier("scramble-random-seed", true); 1281 scrambleRandomSeed.setArgumentGroupName( 1282 INFO_LDAPSEARCH_ARG_GROUP_TRANSFORMATIONS.get()); 1283 parser.addArgument(scrambleRandomSeed); 1284 1285 renameAttributeFrom = new StringArgument(null, "renameAttributeFrom", false, 1286 0, INFO_PLACEHOLDER_ATTR.get(), 1287 INFO_LDAPSEARCH_ARG_DESCRIPTION_RENAME_ATTRIBUTE_FROM.get()); 1288 renameAttributeFrom.addLongIdentifier("rename-attribute-from", true); 1289 renameAttributeFrom.setArgumentGroupName( 1290 INFO_LDAPSEARCH_ARG_GROUP_TRANSFORMATIONS.get()); 1291 parser.addArgument(renameAttributeFrom); 1292 1293 renameAttributeTo = new StringArgument(null, "renameAttributeTo", false, 1294 0, INFO_PLACEHOLDER_ATTR.get(), 1295 INFO_LDAPSEARCH_ARG_DESCRIPTION_RENAME_ATTRIBUTE_TO.get()); 1296 renameAttributeTo.addLongIdentifier("rename-attribute-to", true); 1297 renameAttributeTo.setArgumentGroupName( 1298 INFO_LDAPSEARCH_ARG_GROUP_TRANSFORMATIONS.get()); 1299 parser.addArgument(renameAttributeTo); 1300 1301 moveSubtreeFrom = new DNArgument(null, "moveSubtreeFrom", false, 0, 1302 INFO_PLACEHOLDER_ATTR.get(), 1303 INFO_LDAPSEARCH_ARG_DESCRIPTION_MOVE_SUBTREE_FROM.get()); 1304 moveSubtreeFrom.addLongIdentifier("move-subtree-from", true); 1305 moveSubtreeFrom.setArgumentGroupName( 1306 INFO_LDAPSEARCH_ARG_GROUP_TRANSFORMATIONS.get()); 1307 parser.addArgument(moveSubtreeFrom); 1308 1309 moveSubtreeTo = new DNArgument(null, "moveSubtreeTo", false, 0, 1310 INFO_PLACEHOLDER_ATTR.get(), 1311 INFO_LDAPSEARCH_ARG_DESCRIPTION_MOVE_SUBTREE_TO.get()); 1312 moveSubtreeTo.addLongIdentifier("move-subtree-to", true); 1313 moveSubtreeTo.setArgumentGroupName( 1314 INFO_LDAPSEARCH_ARG_GROUP_TRANSFORMATIONS.get()); 1315 parser.addArgument(moveSubtreeTo); 1316 1317 1318 // The "--scriptFriendly" argument is provided for compatibility with legacy 1319 // ldapsearch tools, but is not actually used by this tool. 1320 final BooleanArgument scriptFriendly = new BooleanArgument(null, 1321 "scriptFriendly", 1, 1322 INFO_LDAPSEARCH_ARG_DESCRIPTION_SCRIPT_FRIENDLY.get()); 1323 scriptFriendly.addLongIdentifier("script-friendly", true); 1324 scriptFriendly.setHidden(true); 1325 parser.addArgument(scriptFriendly); 1326 1327 1328 // The "-V" / "--ldapVersion" argument is provided for compatibility with 1329 // legacy ldapsearch tools, but is not actually used by this tool. 1330 final IntegerArgument ldapVersion = new IntegerArgument('V', "ldapVersion", 1331 false, 1, null, INFO_LDAPSEARCH_ARG_DESCRIPTION_LDAP_VERSION.get()); 1332 ldapVersion.addLongIdentifier("ldap-version", true); 1333 ldapVersion.setHidden(true); 1334 parser.addArgument(ldapVersion); 1335 1336 1337 // The baseDN and ldapURLFile arguments can't be used together. 1338 parser.addExclusiveArgumentSet(baseDN, ldapURLFile); 1339 1340 // The scope and ldapURLFile arguments can't be used together. 1341 parser.addExclusiveArgumentSet(scope, ldapURLFile); 1342 1343 // The requestedAttribute and ldapURLFile arguments can't be used together. 1344 parser.addExclusiveArgumentSet(requestedAttribute, ldapURLFile); 1345 1346 // The filter and ldapURLFile arguments can't be used together. 1347 parser.addExclusiveArgumentSet(filter, ldapURLFile); 1348 1349 // The filterFile and ldapURLFile arguments can't be used together. 1350 parser.addExclusiveArgumentSet(filterFile, ldapURLFile); 1351 1352 // The followReferrals and manageDsaIT arguments can't be used together. 1353 parser.addExclusiveArgumentSet(followReferrals, manageDsaIT); 1354 1355 // The persistent search argument can't be used with either the filterFile 1356 // or ldapURLFile arguments. 1357 parser.addExclusiveArgumentSet(persistentSearch, filterFile); 1358 parser.addExclusiveArgumentSet(persistentSearch, ldapURLFile); 1359 1360 // The draft-ietf-ldup-subentry and RFC 3672 subentries controls cannot be 1361 // used together. 1362 parser.addExclusiveArgumentSet(draftLDUPSubentries, rfc3672Subentries); 1363 1364 // The realAttributesOnly and virtualAttributesOnly arguments can't be used 1365 // together. 1366 parser.addExclusiveArgumentSet(realAttributesOnly, virtualAttributesOnly); 1367 1368 // The simplePageSize and virtualListView arguments can't be used together. 1369 parser.addExclusiveArgumentSet(simplePageSize, virtualListView); 1370 1371 // The terse and verbose arguments can't be used together. 1372 parser.addExclusiveArgumentSet(terse, verbose); 1373 1374 // The getEffectiveRightsAttribute argument requires the 1375 // getEffectiveRightsAuthzID argument. 1376 parser.addDependentArgumentSet(getEffectiveRightsAttribute, 1377 getEffectiveRightsAuthzID); 1378 1379 // The virtualListView argument requires the sortOrder argument. 1380 parser.addDependentArgumentSet(virtualListView, sortOrder); 1381 1382 // The rejectUnindexedSearch and permitUnindexedSearch arguments can't be 1383 // used together. 1384 parser.addExclusiveArgumentSet(rejectUnindexedSearch, 1385 permitUnindexedSearch); 1386 1387 // The separateOutputFilePerSearch argument requires the outputFile 1388 // argument. It also requires either the filter, filterFile or ldapURLFile 1389 // argument. 1390 parser.addDependentArgumentSet(separateOutputFilePerSearch, outputFile); 1391 parser.addDependentArgumentSet(separateOutputFilePerSearch, filter, 1392 filterFile, ldapURLFile); 1393 1394 // The teeResultsToStandardOut argument requires the outputFile argument. 1395 parser.addDependentArgumentSet(teeResultsToStandardOut, outputFile); 1396 1397 // The wrapColumn and dontWrap arguments must not be used together. 1398 parser.addExclusiveArgumentSet(wrapColumn, dontWrap); 1399 1400 // All arguments that specifically pertain to join processing can only be 1401 // used if the joinRule argument is provided. 1402 parser.addDependentArgumentSet(joinBaseDN, joinRule); 1403 parser.addDependentArgumentSet(joinScope, joinRule); 1404 parser.addDependentArgumentSet(joinSizeLimit, joinRule); 1405 parser.addDependentArgumentSet(joinFilter, joinRule); 1406 parser.addDependentArgumentSet(joinRequestedAttribute, joinRule); 1407 parser.addDependentArgumentSet(joinRequireMatch, joinRule); 1408 1409 // The countEntries argument must not be used in conjunction with the 1410 // filter, filterFile, LDAPURLFile, or persistentSearch arguments. 1411 parser.addExclusiveArgumentSet(countEntries, filter); 1412 parser.addExclusiveArgumentSet(countEntries, filterFile); 1413 parser.addExclusiveArgumentSet(countEntries, ldapURLFile); 1414 parser.addExclusiveArgumentSet(countEntries, persistentSearch); 1415 1416 1417 // The hideRedactedValueCount argument requires the redactAttribute 1418 // argument. 1419 parser.addDependentArgumentSet(hideRedactedValueCount, redactAttribute); 1420 1421 // The scrambleJSONField and scrambleRandomSeed arguments require the 1422 // scrambleAttribute argument. 1423 parser.addDependentArgumentSet(scrambleJSONField, scrambleAttribute); 1424 parser.addDependentArgumentSet(scrambleRandomSeed, scrambleAttribute); 1425 1426 // The renameAttributeFrom and renameAttributeTo arguments must be provided 1427 // together. 1428 parser.addDependentArgumentSet(renameAttributeFrom, renameAttributeTo); 1429 parser.addDependentArgumentSet(renameAttributeTo, renameAttributeFrom); 1430 1431 // The moveSubtreeFrom and moveSubtreeTo arguments must be provided 1432 // together. 1433 parser.addDependentArgumentSet(moveSubtreeFrom, moveSubtreeTo); 1434 parser.addDependentArgumentSet(moveSubtreeTo, moveSubtreeFrom); 1435 1436 1437 // The compressOutput argument can only be used if an output file is 1438 // specified and results aren't going to be teed. 1439 parser.addDependentArgumentSet(compressOutput, outputFile); 1440 parser.addExclusiveArgumentSet(compressOutput, teeResultsToStandardOut); 1441 1442 1443 // The encryptOutput argument can only be used if an output file is 1444 // specified and results aren't going to be teed. 1445 parser.addDependentArgumentSet(encryptOutput, outputFile); 1446 parser.addExclusiveArgumentSet(encryptOutput, teeResultsToStandardOut); 1447 1448 1449 // The encryptionPassphraseFile argument can only be used if the 1450 // encryptOutput argument is also provided. 1451 parser.addDependentArgumentSet(encryptionPassphraseFile, encryptOutput); 1452 } 1453 1454 1455 1456 /** 1457 * {@inheritDoc} 1458 */ 1459 @Override() 1460 @NotNull() 1461 protected List<Control> getBindControls() 1462 { 1463 final ArrayList<Control> bindControls = new ArrayList<>(10); 1464 1465 if (bindControl.isPresent()) 1466 { 1467 bindControls.addAll(bindControl.getValues()); 1468 } 1469 1470 if (authorizationIdentity.isPresent()) 1471 { 1472 bindControls.add(new AuthorizationIdentityRequestControl(false)); 1473 } 1474 1475 if (generateAccessToken.isPresent()) 1476 { 1477 bindControls.add(new GenerateAccessTokenRequestControl()); 1478 } 1479 1480 if (getAuthorizationEntryAttribute.isPresent()) 1481 { 1482 bindControls.add(new GetAuthorizationEntryRequestControl(true, true, 1483 getAuthorizationEntryAttribute.getValues())); 1484 } 1485 1486 if (getRecentLoginHistory.isPresent()) 1487 { 1488 bindControls.add(new GetRecentLoginHistoryRequestControl()); 1489 } 1490 1491 if (getUserResourceLimits.isPresent()) 1492 { 1493 bindControls.add(new GetUserResourceLimitsRequestControl()); 1494 } 1495 1496 if (usePasswordPolicyControl.isPresent()) 1497 { 1498 bindControls.add(new PasswordPolicyRequestControl()); 1499 } 1500 1501 if (suppressOperationalAttributeUpdates.isPresent()) 1502 { 1503 final EnumSet<SuppressType> suppressTypes = 1504 EnumSet.noneOf(SuppressType.class); 1505 for (final String s : suppressOperationalAttributeUpdates.getValues()) 1506 { 1507 if (s.equalsIgnoreCase("last-access-time")) 1508 { 1509 suppressTypes.add(SuppressType.LAST_ACCESS_TIME); 1510 } 1511 else if (s.equalsIgnoreCase("last-login-time")) 1512 { 1513 suppressTypes.add(SuppressType.LAST_LOGIN_TIME); 1514 } 1515 else if (s.equalsIgnoreCase("last-login-ip")) 1516 { 1517 suppressTypes.add(SuppressType.LAST_LOGIN_IP); 1518 } 1519 } 1520 1521 bindControls.add(new SuppressOperationalAttributeUpdateRequestControl( 1522 suppressTypes)); 1523 } 1524 1525 if (useJSONFormattedRequestControls.isPresent()) 1526 { 1527 final JSONFormattedRequestControl jsonFormattedRequestControl = 1528 JSONFormattedRequestControl.createWithControls(true, bindControls); 1529 bindControls.clear(); 1530 bindControls.add(jsonFormattedRequestControl); 1531 } 1532 1533 return bindControls; 1534 } 1535 1536 1537 1538 /** 1539 * {@inheritDoc} 1540 */ 1541 @Override() 1542 protected boolean supportsMultipleServers() 1543 { 1544 // We will support providing information about multiple servers. This tool 1545 // will not communicate with multiple servers concurrently, but it can 1546 // accept information about multiple servers in the event that multiple 1547 // searches are to be performed and a server goes down in the middle of 1548 // those searches. In this case, we can resume processing on a 1549 // newly-created connection, possibly to a different server. 1550 return true; 1551 } 1552 1553 1554 1555 /** 1556 * {@inheritDoc} 1557 */ 1558 @Override() 1559 public void doExtendedNonLDAPArgumentValidation() 1560 throws ArgumentException 1561 { 1562 // If wrapColumn was provided, then use its value. Otherwise, if dontWrap 1563 // was provided, then use that. 1564 if (wrapColumn.isPresent()) 1565 { 1566 final int wc = wrapColumn.getValue(); 1567 if (wc <= 0) 1568 { 1569 WRAP_COLUMN = Integer.MAX_VALUE; 1570 } 1571 else 1572 { 1573 WRAP_COLUMN = wc; 1574 } 1575 } 1576 else if (dontWrap.isPresent()) 1577 { 1578 WRAP_COLUMN = Integer.MAX_VALUE; 1579 } 1580 1581 1582 // If the ldapURLFile argument was provided, then there must not be any 1583 // trailing arguments. 1584 final List<String> trailingArgs = parser.getTrailingArguments(); 1585 if (ldapURLFile.isPresent()) 1586 { 1587 if (! trailingArgs.isEmpty()) 1588 { 1589 throw new ArgumentException( 1590 ERR_LDAPSEARCH_TRAILING_ARGS_WITH_URL_FILE.get( 1591 ldapURLFile.getIdentifierString())); 1592 } 1593 } 1594 1595 1596 // If the filter or filterFile argument was provided, then there may 1597 // optionally be trailing arguments, but the first trailing argument must 1598 // not be a filter. 1599 if (filter.isPresent() || filterFile.isPresent()) 1600 { 1601 if (! trailingArgs.isEmpty()) 1602 { 1603 try 1604 { 1605 Filter.create(trailingArgs.get(0)); 1606 throw new ArgumentException( 1607 ERR_LDAPSEARCH_TRAILING_FILTER_WITH_FILTER_FILE.get( 1608 filterFile.getIdentifierString())); 1609 } 1610 catch (final LDAPException le) 1611 { 1612 // This is the normal condition. Not even worth debugging the 1613 // exception. 1614 } 1615 } 1616 } 1617 1618 1619 // If none of the ldapURLFile, filter, or filterFile arguments was provided, 1620 // then there must be at least one trailing argument, and the first trailing 1621 // argument must be a valid search filter. 1622 if (! (ldapURLFile.isPresent() || filter.isPresent() || 1623 filterFile.isPresent())) 1624 { 1625 if (trailingArgs.isEmpty()) 1626 { 1627 throw new ArgumentException(ERR_LDAPSEARCH_NO_TRAILING_ARGS.get( 1628 filterFile.getIdentifierString(), 1629 ldapURLFile.getIdentifierString())); 1630 } 1631 1632 try 1633 { 1634 Filter.create(trailingArgs.get(0)); 1635 } 1636 catch (final Exception e) 1637 { 1638 Debug.debugException(e); 1639 throw new ArgumentException( 1640 ERR_LDAPSEARCH_FIRST_TRAILING_ARG_NOT_FILTER.get( 1641 trailingArgs.get(0)), 1642 e); 1643 } 1644 } 1645 1646 1647 // There should never be a case in which a trailing argument starts with a 1648 // dash, and it's probably an attempt to use a named argument but that was 1649 // inadvertently put after the filter. Warn about the problem, but don't 1650 // fail. 1651 for (final String s : trailingArgs) 1652 { 1653 if (s.startsWith("-")) 1654 { 1655 commentToErr(WARN_LDAPSEARCH_TRAILING_ARG_STARTS_WITH_DASH.get(s)); 1656 break; 1657 } 1658 } 1659 1660 1661 // If any matched values filters are specified, then validate them and 1662 // pre-create the matched values request control. 1663 if (matchedValuesFilter.isPresent()) 1664 { 1665 final List<Filter> filterList = matchedValuesFilter.getValues(); 1666 final MatchedValuesFilter[] matchedValuesFilters = 1667 new MatchedValuesFilter[filterList.size()]; 1668 for (int i=0; i < matchedValuesFilters.length; i++) 1669 { 1670 try 1671 { 1672 matchedValuesFilters[i] = 1673 MatchedValuesFilter.create(filterList.get(i)); 1674 } 1675 catch (final Exception e) 1676 { 1677 Debug.debugException(e); 1678 throw new ArgumentException( 1679 ERR_LDAPSEARCH_INVALID_MATCHED_VALUES_FILTER.get( 1680 filterList.get(i).toString()), 1681 e); 1682 } 1683 } 1684 1685 matchedValuesRequestControl = 1686 new MatchedValuesRequestControl(true, matchedValuesFilters); 1687 } 1688 1689 1690 // If we should use the matching entry count request control, then validate 1691 // the argument value and pre-create the control. 1692 if (matchingEntryCountControl.isPresent()) 1693 { 1694 final MatchingEntryCountRequestControlProperties properties = 1695 new MatchingEntryCountRequestControlProperties(); 1696 1697 Integer examineCount = null; 1698 try 1699 { 1700 for (final String element : 1701 matchingEntryCountControl.getValue().toLowerCase().split(":")) 1702 { 1703 if (element.startsWith("examinecount=")) 1704 { 1705 examineCount = Integer.parseInt(element.substring(13)); 1706 } 1707 else if (element.equals("allowunindexed")) 1708 { 1709 properties.setProcessSearchIfUnindexed(true); 1710 } 1711 else if (element.equals("alwaysexamine")) 1712 { 1713 properties.setAlwaysExamineCandidates(true); 1714 } 1715 else if (element.equals("skipresolvingexplodedindexes")) 1716 { 1717 properties.setSkipResolvingExplodedIndexes(true); 1718 } 1719 else if (element.startsWith("fastshortcircuitthreshold=")) 1720 { 1721 properties.setFastShortCircuitThreshold( 1722 Long.parseLong(element.substring(26))); 1723 } 1724 else if (element.startsWith("slowshortcircuitthreshold=")) 1725 { 1726 properties.setSlowShortCircuitThreshold( 1727 Long.parseLong(element.substring(26))); 1728 } 1729 else if (element.equals("extendedresponsedata")) 1730 { 1731 properties.setIncludeExtendedResponseData(true); 1732 } 1733 else if (element.equals("debug")) 1734 { 1735 properties.setIncludeDebugInfo(true); 1736 } 1737 else 1738 { 1739 throw new ArgumentException( 1740 ERR_LDAPSEARCH_MATCHING_ENTRY_COUNT_INVALID_VALUE.get( 1741 matchingEntryCountControl.getIdentifierString())); 1742 } 1743 } 1744 } 1745 catch (final ArgumentException ae) 1746 { 1747 Debug.debugException(ae); 1748 throw ae; 1749 } 1750 catch (final Exception e) 1751 { 1752 Debug.debugException(e); 1753 throw new ArgumentException( 1754 ERR_LDAPSEARCH_MATCHING_ENTRY_COUNT_INVALID_VALUE.get( 1755 matchingEntryCountControl.getIdentifierString()), 1756 e); 1757 } 1758 1759 if (examineCount == null) 1760 { 1761 throw new ArgumentException( 1762 ERR_LDAPSEARCH_MATCHING_ENTRY_COUNT_INVALID_VALUE.get( 1763 matchingEntryCountControl.getIdentifierString())); 1764 } 1765 else 1766 { 1767 properties.setMaxCandidatesToExamine(examineCount); 1768 } 1769 1770 matchingEntryCountRequestControl = 1771 new MatchingEntryCountRequestControl(true, properties); 1772 } 1773 1774 1775 // If we should include the override search limits request control, then 1776 // validate the provided values. 1777 if (overrideSearchLimit.isPresent()) 1778 { 1779 final LinkedHashMap<String,String> properties = 1780 new LinkedHashMap<>(StaticUtils.computeMapCapacity(10)); 1781 for (final String value : overrideSearchLimit.getValues()) 1782 { 1783 final int equalPos = value.indexOf('='); 1784 if (equalPos < 0) 1785 { 1786 throw new ArgumentException( 1787 ERR_LDAPSEARCH_OVERRIDE_LIMIT_NO_EQUAL.get( 1788 overrideSearchLimit.getIdentifierString())); 1789 } 1790 else if (equalPos == 0) 1791 { 1792 throw new ArgumentException( 1793 ERR_LDAPSEARCH_OVERRIDE_LIMIT_EMPTY_PROPERTY_NAME.get( 1794 overrideSearchLimit.getIdentifierString())); 1795 } 1796 1797 final String propertyName = value.substring(0, equalPos); 1798 if (properties.containsKey(propertyName)) 1799 { 1800 throw new ArgumentException( 1801 ERR_LDAPSEARCH_OVERRIDE_LIMIT_DUPLICATE_PROPERTY_NAME.get( 1802 overrideSearchLimit.getIdentifierString(), propertyName)); 1803 } 1804 1805 if (equalPos == (value.length() - 1)) 1806 { 1807 throw new ArgumentException( 1808 ERR_LDAPSEARCH_OVERRIDE_LIMIT_EMPTY_PROPERTY_VALUE.get( 1809 overrideSearchLimit.getIdentifierString(), propertyName)); 1810 } 1811 1812 properties.put(propertyName, value.substring(equalPos+1)); 1813 } 1814 1815 overrideSearchLimitsRequestControl = 1816 new OverrideSearchLimitsRequestControl(properties, false); 1817 } 1818 1819 1820 // If we should use the persistent search request control, then validate 1821 // the argument value and pre-create the control. 1822 if (persistentSearch.isPresent()) 1823 { 1824 boolean changesOnly = true; 1825 boolean returnECs = true; 1826 EnumSet<PersistentSearchChangeType> changeTypes = 1827 EnumSet.allOf(PersistentSearchChangeType.class); 1828 try 1829 { 1830 final String[] elements = 1831 persistentSearch.getValue().toLowerCase().split(":"); 1832 if (elements.length == 0) 1833 { 1834 throw new ArgumentException( 1835 ERR_LDAPSEARCH_PERSISTENT_SEARCH_INVALID_VALUE.get( 1836 persistentSearch.getIdentifierString())); 1837 } 1838 1839 final String header = StaticUtils.toLowerCase(elements[0]); 1840 if (! (header.equals("ps") || header.equals("persist") || 1841 header.equals("persistent") || header.equals("psearch") || 1842 header.equals("persistentsearch"))) 1843 { 1844 throw new ArgumentException( 1845 ERR_LDAPSEARCH_PERSISTENT_SEARCH_INVALID_VALUE.get( 1846 persistentSearch.getIdentifierString())); 1847 } 1848 1849 if (elements.length > 1) 1850 { 1851 final String ctString = StaticUtils.toLowerCase(elements[1]); 1852 if (ctString.equals("any")) 1853 { 1854 changeTypes = EnumSet.allOf(PersistentSearchChangeType.class); 1855 } 1856 else 1857 { 1858 changeTypes.clear(); 1859 for (final String t : ctString.split(",")) 1860 { 1861 if (t.equals("add")) 1862 { 1863 changeTypes.add(PersistentSearchChangeType.ADD); 1864 } 1865 else if (t.equals("del") || t.equals("delete")) 1866 { 1867 changeTypes.add(PersistentSearchChangeType.DELETE); 1868 } 1869 else if (t.equals("mod") || t.equals("modify")) 1870 { 1871 changeTypes.add(PersistentSearchChangeType.MODIFY); 1872 } 1873 else if (t.equals("moddn") || t.equals("modrdn") || 1874 t.equals("modifydn") || t.equals("modifyrdn")) 1875 { 1876 changeTypes.add(PersistentSearchChangeType.MODIFY_DN); 1877 } 1878 else 1879 { 1880 throw new ArgumentException( 1881 ERR_LDAPSEARCH_PERSISTENT_SEARCH_INVALID_VALUE.get( 1882 persistentSearch.getIdentifierString())); 1883 } 1884 } 1885 } 1886 } 1887 1888 if (elements.length > 2) 1889 { 1890 if (elements[2].equalsIgnoreCase("true") || elements[2].equals("1")) 1891 { 1892 changesOnly = true; 1893 } 1894 else if (elements[2].equalsIgnoreCase("false") || 1895 elements[2].equals("0")) 1896 { 1897 changesOnly = false; 1898 } 1899 else 1900 { 1901 throw new ArgumentException( 1902 ERR_LDAPSEARCH_PERSISTENT_SEARCH_INVALID_VALUE.get( 1903 persistentSearch.getIdentifierString())); 1904 } 1905 } 1906 1907 if (elements.length > 3) 1908 { 1909 if (elements[3].equalsIgnoreCase("true") || elements[3].equals("1")) 1910 { 1911 returnECs = true; 1912 } 1913 else if (elements[3].equalsIgnoreCase("false") || 1914 elements[3].equals("0")) 1915 { 1916 returnECs = false; 1917 } 1918 else 1919 { 1920 throw new ArgumentException( 1921 ERR_LDAPSEARCH_PERSISTENT_SEARCH_INVALID_VALUE.get( 1922 persistentSearch.getIdentifierString())); 1923 } 1924 } 1925 } 1926 catch (final ArgumentException ae) 1927 { 1928 Debug.debugException(ae); 1929 throw ae; 1930 } 1931 catch (final Exception e) 1932 { 1933 Debug.debugException(e); 1934 throw new ArgumentException( 1935 ERR_LDAPSEARCH_PERSISTENT_SEARCH_INVALID_VALUE.get( 1936 persistentSearch.getIdentifierString()), 1937 e); 1938 } 1939 1940 persistentSearchRequestControl = new PersistentSearchRequestControl( 1941 changeTypes, changesOnly, returnECs, true); 1942 } 1943 1944 1945 // If we should use the server-side sort request control, then validate the 1946 // sort order and pre-create the control. 1947 if (sortOrder.isPresent()) 1948 { 1949 final ArrayList<SortKey> sortKeyList = new ArrayList<>(5); 1950 final StringTokenizer tokenizer = 1951 new StringTokenizer(sortOrder.getValue(), ", "); 1952 while (tokenizer.hasMoreTokens()) 1953 { 1954 final String token = tokenizer.nextToken(); 1955 1956 final boolean ascending; 1957 String attributeName; 1958 if (token.startsWith("-")) 1959 { 1960 ascending = false; 1961 attributeName = token.substring(1); 1962 } 1963 else if (token.startsWith("+")) 1964 { 1965 ascending = true; 1966 attributeName = token.substring(1); 1967 } 1968 else 1969 { 1970 ascending = true; 1971 attributeName = token; 1972 } 1973 1974 final String matchingRuleID; 1975 final int colonPos = attributeName.indexOf(':'); 1976 if (colonPos >= 0) 1977 { 1978 matchingRuleID = attributeName.substring(colonPos+1); 1979 attributeName = attributeName.substring(0, colonPos); 1980 } 1981 else 1982 { 1983 matchingRuleID = null; 1984 } 1985 1986 final StringBuilder invalidReason = new StringBuilder(); 1987 if (! PersistUtils.isValidLDAPName(attributeName, false, invalidReason)) 1988 { 1989 throw new ArgumentException( 1990 ERR_LDAPSEARCH_SORT_ORDER_INVALID_VALUE.get( 1991 sortOrder.getIdentifierString())); 1992 } 1993 1994 sortKeyList.add( 1995 new SortKey(attributeName, matchingRuleID, (! ascending))); 1996 } 1997 1998 if (sortKeyList.isEmpty()) 1999 { 2000 throw new ArgumentException( 2001 ERR_LDAPSEARCH_SORT_ORDER_INVALID_VALUE.get( 2002 sortOrder.getIdentifierString())); 2003 } 2004 2005 final SortKey[] sortKeyArray = new SortKey[sortKeyList.size()]; 2006 sortKeyList.toArray(sortKeyArray); 2007 2008 sortRequestControl = new ServerSideSortRequestControl(sortKeyArray); 2009 } 2010 2011 2012 // If we should use the virtual list view request control, then validate the 2013 // argument value and pre-create the control. 2014 if (virtualListView.isPresent()) 2015 { 2016 try 2017 { 2018 final String[] elements = virtualListView.getValue().split(":"); 2019 if (elements.length == 4) 2020 { 2021 vlvRequestControl = new VirtualListViewRequestControl( 2022 Integer.parseInt(elements[2]), Integer.parseInt(elements[0]), 2023 Integer.parseInt(elements[1]), Integer.parseInt(elements[3]), 2024 null); 2025 } 2026 else if (elements.length == 3) 2027 { 2028 vlvRequestControl = new VirtualListViewRequestControl(elements[2], 2029 Integer.parseInt(elements[0]), Integer.parseInt(elements[1]), 2030 null); 2031 } 2032 else 2033 { 2034 throw new ArgumentException( 2035 ERR_LDAPSEARCH_VLV_INVALID_VALUE.get( 2036 virtualListView.getIdentifierString())); 2037 } 2038 } 2039 catch (final ArgumentException ae) 2040 { 2041 Debug.debugException(ae); 2042 throw ae; 2043 } 2044 catch (final Exception e) 2045 { 2046 Debug.debugException(e); 2047 throw new ArgumentException( 2048 ERR_LDAPSEARCH_VLV_INVALID_VALUE.get( 2049 virtualListView.getIdentifierString()), 2050 e); 2051 } 2052 } 2053 2054 2055 // If we should use the LDAP join request control, then validate and 2056 // pre-create that control. 2057 if (joinRule.isPresent()) 2058 { 2059 final JoinRule rule; 2060 try 2061 { 2062 final String[] elements = joinRule.getValue().toLowerCase().split(":"); 2063 final String ruleName = StaticUtils.toLowerCase(elements[0]); 2064 if (ruleName.equals("dn")) 2065 { 2066 rule = JoinRule.createDNJoin(elements[1]); 2067 } 2068 else if (ruleName.equals("reverse-dn") || ruleName.equals("reversedn")) 2069 { 2070 rule = JoinRule.createReverseDNJoin(elements[1]); 2071 } 2072 else if (ruleName.equals("equals") || ruleName.equals("equality")) 2073 { 2074 rule = JoinRule.createEqualityJoin(elements[1], elements[2], false); 2075 } 2076 else if (ruleName.equals("contains") || ruleName.equals("substring")) 2077 { 2078 rule = JoinRule.createContainsJoin(elements[1], elements[2], false); 2079 } 2080 else 2081 { 2082 throw new ArgumentException( 2083 ERR_LDAPSEARCH_JOIN_RULE_INVALID_VALUE.get( 2084 joinRule.getIdentifierString())); 2085 } 2086 } 2087 catch (final ArgumentException ae) 2088 { 2089 Debug.debugException(ae); 2090 throw ae; 2091 } 2092 catch (final Exception e) 2093 { 2094 Debug.debugException(e); 2095 throw new ArgumentException( 2096 ERR_LDAPSEARCH_JOIN_RULE_INVALID_VALUE.get( 2097 joinRule.getIdentifierString()), 2098 e); 2099 } 2100 2101 final JoinBaseDN joinBase; 2102 if (joinBaseDN.isPresent()) 2103 { 2104 final String s = StaticUtils.toLowerCase(joinBaseDN.getValue()); 2105 if (s.equals("search-base") || s.equals("search-base-dn")) 2106 { 2107 joinBase = JoinBaseDN.createUseSearchBaseDN(); 2108 } 2109 else if (s.equals("source-entry-dn") || s.equals("source-dn")) 2110 { 2111 joinBase = JoinBaseDN.createUseSourceEntryDN(); 2112 } 2113 else 2114 { 2115 try 2116 { 2117 final DN dn = new DN(joinBaseDN.getValue()); 2118 joinBase = JoinBaseDN.createUseCustomBaseDN(joinBaseDN.getValue()); 2119 } 2120 catch (final Exception e) 2121 { 2122 Debug.debugException(e); 2123 throw new ArgumentException( 2124 ERR_LDAPSEARCH_JOIN_BASE_DN_INVALID_VALUE.get( 2125 joinBaseDN.getIdentifierString()), 2126 e); 2127 } 2128 } 2129 } 2130 else 2131 { 2132 joinBase = JoinBaseDN.createUseSearchBaseDN(); 2133 } 2134 2135 final String[] joinAttrs; 2136 if (joinRequestedAttribute.isPresent()) 2137 { 2138 final List<String> valueList = joinRequestedAttribute.getValues(); 2139 joinAttrs = new String[valueList.size()]; 2140 valueList.toArray(joinAttrs); 2141 } 2142 else 2143 { 2144 joinAttrs = null; 2145 } 2146 2147 joinRequestControl = new JoinRequestControl(new JoinRequestValue(rule, 2148 joinBase, joinScope.getValue(), DereferencePolicy.NEVER, 2149 joinSizeLimit.getValue(), joinFilter.getValue(), joinAttrs, 2150 joinRequireMatch.isPresent(), null)); 2151 } 2152 2153 2154 // If we should use the route to backend set request control, then validate 2155 // and pre-create those controls. 2156 if (routeToBackendSet.isPresent()) 2157 { 2158 final List<String> values = routeToBackendSet.getValues(); 2159 final Map<String,List<String>> idsByRP = new LinkedHashMap<>( 2160 StaticUtils.computeMapCapacity(values.size())); 2161 for (final String value : values) 2162 { 2163 final int colonPos = value.indexOf(':'); 2164 if (colonPos <= 0) 2165 { 2166 throw new ArgumentException( 2167 ERR_LDAPSEARCH_ROUTE_TO_BACKEND_SET_INVALID_FORMAT.get(value, 2168 routeToBackendSet.getIdentifierString())); 2169 } 2170 2171 final String rpID = value.substring(0, colonPos); 2172 final String bsID = value.substring(colonPos+1); 2173 2174 List<String> idsForRP = idsByRP.get(rpID); 2175 if (idsForRP == null) 2176 { 2177 idsForRP = new ArrayList<>(values.size()); 2178 idsByRP.put(rpID, idsForRP); 2179 } 2180 idsForRP.add(bsID); 2181 } 2182 2183 for (final Map.Entry<String,List<String>> e : idsByRP.entrySet()) 2184 { 2185 final String rpID = e.getKey(); 2186 final List<String> bsIDs = e.getValue(); 2187 routeToBackendSetRequestControls.add( 2188 RouteToBackendSetRequestControl.createAbsoluteRoutingRequest(true, 2189 rpID, bsIDs)); 2190 } 2191 } 2192 2193 2194 // Parse the dereference policy. 2195 final String derefStr = 2196 StaticUtils.toLowerCase(dereferencePolicy.getValue()); 2197 if (derefStr.equals("always")) 2198 { 2199 derefPolicy = DereferencePolicy.ALWAYS; 2200 } 2201 else if (derefStr.equals("search")) 2202 { 2203 derefPolicy = DereferencePolicy.SEARCHING; 2204 } 2205 else if (derefStr.equals("find")) 2206 { 2207 derefPolicy = DereferencePolicy.FINDING; 2208 } 2209 else 2210 { 2211 derefPolicy = DereferencePolicy.NEVER; 2212 } 2213 2214 2215 // If the --proxyAs argument was provided, then make sure its value is 2216 // properly formatted. 2217 if (proxyAs.isPresent()) 2218 { 2219 final String proxyAsValue = proxyAs.getValue(); 2220 final String lowerProxyAsValue = StaticUtils.toLowerCase(proxyAsValue); 2221 if (lowerProxyAsValue.startsWith("dn:")) 2222 { 2223 final String dnString = proxyAsValue.substring(3); 2224 if (! DN.isValidDN(dnString)) 2225 { 2226 throw new ArgumentException(ERR_LDAPSEARCH_PROXY_AS_DN_NOT_DN.get( 2227 proxyAs.getIdentifierString(), dnString)); 2228 } 2229 } 2230 else if (! lowerProxyAsValue.startsWith("u:")) 2231 { 2232 throw new ArgumentException( 2233 ERR_LDAPSEARCH_PROXY_AS_VALUE_MISSING_PREFIX.get( 2234 proxyAs.getIdentifierString())); 2235 } 2236 } 2237 2238 2239 // See if any entry transformations need to be applied. 2240 final ArrayList<EntryTransformation> transformations = new ArrayList<>(5); 2241 if (excludeAttribute.isPresent()) 2242 { 2243 transformations.add(new ExcludeAttributeTransformation(null, 2244 excludeAttribute.getValues())); 2245 } 2246 2247 if (redactAttribute.isPresent()) 2248 { 2249 transformations.add(new RedactAttributeTransformation(null, true, 2250 (! hideRedactedValueCount.isPresent()), 2251 redactAttribute.getValues())); 2252 } 2253 2254 if (scrambleAttribute.isPresent()) 2255 { 2256 final Long randomSeed; 2257 if (scrambleRandomSeed.isPresent()) 2258 { 2259 randomSeed = scrambleRandomSeed.getValue().longValue(); 2260 } 2261 else 2262 { 2263 randomSeed = null; 2264 } 2265 2266 transformations.add(new ScrambleAttributeTransformation(null, randomSeed, 2267 true, scrambleAttribute.getValues(), scrambleJSONField.getValues())); 2268 } 2269 2270 if (renameAttributeFrom.isPresent()) 2271 { 2272 if (renameAttributeFrom.getNumOccurrences() != 2273 renameAttributeTo.getNumOccurrences()) 2274 { 2275 throw new ArgumentException( 2276 ERR_LDAPSEARCH_RENAME_ATTRIBUTE_MISMATCH.get()); 2277 } 2278 2279 final Iterator<String> sourceIterator = 2280 renameAttributeFrom.getValues().iterator(); 2281 final Iterator<String> targetIterator = 2282 renameAttributeTo.getValues().iterator(); 2283 while (sourceIterator.hasNext()) 2284 { 2285 transformations.add(new RenameAttributeTransformation(null, 2286 sourceIterator.next(), targetIterator.next(), true)); 2287 } 2288 } 2289 2290 if (moveSubtreeFrom.isPresent()) 2291 { 2292 if (moveSubtreeFrom.getNumOccurrences() != 2293 moveSubtreeTo.getNumOccurrences()) 2294 { 2295 throw new ArgumentException(ERR_LDAPSEARCH_MOVE_SUBTREE_MISMATCH.get()); 2296 } 2297 2298 final Iterator<DN> sourceIterator = 2299 moveSubtreeFrom.getValues().iterator(); 2300 final Iterator<DN> targetIterator = moveSubtreeTo.getValues().iterator(); 2301 while (sourceIterator.hasNext()) 2302 { 2303 transformations.add(new MoveSubtreeTransformation(sourceIterator.next(), 2304 targetIterator.next())); 2305 } 2306 } 2307 2308 if (! transformations.isEmpty()) 2309 { 2310 entryTransformations = transformations; 2311 } 2312 2313 2314 // Create the result writer. 2315 final String outputFormatStr = 2316 StaticUtils.toLowerCase(outputFormat.getValue()); 2317 if (outputFormatStr.equals("json")) 2318 { 2319 resultWriter = new JSONLDAPResultWriter(getOutStream()); 2320 } 2321 else if (outputFormatStr.equals("csv") || 2322 outputFormatStr.equals("multi-valued-csv") || 2323 outputFormatStr.equals("tab-delimited") || 2324 outputFormatStr.equals("multi-valued-tab-delimited")) 2325 { 2326 // These output formats cannot be used with the --ldapURLFile argument. 2327 if (ldapURLFile.isPresent()) 2328 { 2329 throw new ArgumentException( 2330 ERR_LDAPSEARCH_OUTPUT_FORMAT_NOT_SUPPORTED_WITH_URLS.get( 2331 outputFormat.getValue(), ldapURLFile.getIdentifierString())); 2332 } 2333 2334 // These output formats require the requested attributes to be specified 2335 // via the --requestedAttribute argument rather than as unnamed trailing 2336 // arguments. 2337 final List<String> requestedAttributes = requestedAttribute.getValues(); 2338 if ((requestedAttributes == null) || requestedAttributes.isEmpty()) 2339 { 2340 throw new ArgumentException( 2341 ERR_LDAPSEARCH_OUTPUT_FORMAT_REQUIRES_REQUESTED_ATTR_ARG.get( 2342 outputFormat.getValue(), 2343 requestedAttribute.getIdentifierString())); 2344 } 2345 2346 switch (trailingArgs.size()) 2347 { 2348 case 0: 2349 // This is fine. 2350 break; 2351 2352 case 1: 2353 // Make sure that the trailing argument is a filter rather than a 2354 // requested attribute. It's sufficient to ensure that neither the 2355 // filter nor filterFile argument was provided. 2356 if (filter.isPresent() || filterFile.isPresent()) 2357 { 2358 throw new ArgumentException( 2359 ERR_LDAPSEARCH_OUTPUT_FORMAT_REQUIRES_REQUESTED_ATTR_ARG.get( 2360 outputFormat.getValue(), 2361 requestedAttribute.getIdentifierString())); 2362 } 2363 break; 2364 2365 default: 2366 throw new ArgumentException( 2367 ERR_LDAPSEARCH_OUTPUT_FORMAT_REQUIRES_REQUESTED_ATTR_ARG.get( 2368 outputFormat.getValue(), 2369 requestedAttribute.getIdentifierString())); 2370 } 2371 2372 final OutputFormat format; 2373 final boolean includeAllValues; 2374 switch (outputFormatStr) 2375 { 2376 case "multi-valued-csv": 2377 format = OutputFormat.CSV; 2378 includeAllValues = true; 2379 break; 2380 case "tab-delimited": 2381 format = OutputFormat.TAB_DELIMITED_TEXT; 2382 includeAllValues = false; 2383 break; 2384 case "multi-valued-tab-delimited": 2385 format = OutputFormat.TAB_DELIMITED_TEXT; 2386 includeAllValues = true; 2387 break; 2388 case "csv": 2389 default: 2390 format = OutputFormat.CSV; 2391 includeAllValues = false; 2392 break; 2393 } 2394 2395 2396 resultWriter = new ColumnBasedLDAPResultWriter(getOutStream(), 2397 format, requestedAttributes, WRAP_COLUMN, includeAllValues); 2398 } 2399 else if (outputFormatStr.equals("dns-only")) 2400 { 2401 resultWriter = new DNsOnlyLDAPResultWriter(getOutStream()); 2402 } 2403 else if (outputFormatStr.equals("values-only")) 2404 { 2405 resultWriter = new ValuesOnlyLDAPResultWriter(getOutStream()); 2406 } 2407 else 2408 { 2409 resultWriter = new LDIFLDAPResultWriter(getOutStream(), WRAP_COLUMN); 2410 } 2411 } 2412 2413 2414 2415 /** 2416 * {@inheritDoc} 2417 */ 2418 @Override() 2419 @NotNull() 2420 public LDAPConnectionOptions getConnectionOptions() 2421 { 2422 final LDAPConnectionOptions options = new LDAPConnectionOptions(); 2423 2424 options.setUseSynchronousMode(true); 2425 options.setFollowReferrals(followReferrals.isPresent()); 2426 options.setUnsolicitedNotificationHandler(this); 2427 options.setResponseTimeoutMillis(0L); 2428 2429 return options; 2430 } 2431 2432 2433 2434 /** 2435 * {@inheritDoc} 2436 */ 2437 @Override() 2438 @NotNull() 2439 public ResultCode doToolProcessing() 2440 { 2441 // If we should encrypt the output, then get the encryption passphrase. 2442 if (encryptOutput.isPresent()) 2443 { 2444 if (encryptionPassphraseFile.isPresent()) 2445 { 2446 try 2447 { 2448 encryptionPassphrase = ToolUtils.readEncryptionPassphraseFromFile( 2449 encryptionPassphraseFile.getValue()); 2450 } 2451 catch (final LDAPException e) 2452 { 2453 Debug.debugException(e); 2454 wrapErr(0, WRAP_COLUMN, e.getMessage()); 2455 return e.getResultCode(); 2456 } 2457 } 2458 else 2459 { 2460 try 2461 { 2462 encryptionPassphrase = ToolUtils.promptForEncryptionPassphrase(false, 2463 true, getOut(), getErr()); 2464 } 2465 catch (final LDAPException e) 2466 { 2467 Debug.debugException(e); 2468 wrapErr(0, WRAP_COLUMN, e.getMessage()); 2469 return e.getResultCode(); 2470 } 2471 } 2472 } 2473 2474 2475 // If we should use an output file, then set that up now. Otherwise, write 2476 // the header to standard output. 2477 if (outputFile.isPresent()) 2478 { 2479 if (! separateOutputFilePerSearch.isPresent()) 2480 { 2481 try 2482 { 2483 OutputStream s = new FileOutputStream(outputFile.getValue()); 2484 2485 if (encryptOutput.isPresent()) 2486 { 2487 s = new PassphraseEncryptedOutputStream(encryptionPassphrase, s); 2488 } 2489 2490 if (compressOutput.isPresent()) 2491 { 2492 s = new GZIPOutputStream(s); 2493 } 2494 2495 if (teeResultsToStandardOut.isPresent()) 2496 { 2497 outStream = new PrintStream(new TeeOutputStream(s, getOut())); 2498 } 2499 else 2500 { 2501 outStream = new PrintStream(s); 2502 } 2503 resultWriter.updateOutputStream(outStream); 2504 errStream = outStream; 2505 } 2506 catch (final Exception e) 2507 { 2508 Debug.debugException(e); 2509 wrapErr(0, WRAP_COLUMN, ERR_LDAPSEARCH_CANNOT_OPEN_OUTPUT_FILE.get( 2510 outputFile.getValue().getAbsolutePath(), 2511 StaticUtils.getExceptionMessage(e))); 2512 return ResultCode.LOCAL_ERROR; 2513 } 2514 2515 resultWriter.writeHeader(); 2516 } 2517 } 2518 else 2519 { 2520 resultWriter.writeHeader(); 2521 } 2522 2523 2524 // Examine the arguments to determine the sets of controls to use for each 2525 // type of request. 2526 final List<Control> searchControls; 2527 try 2528 { 2529 searchControls = getSearchControls(); 2530 } 2531 catch (final LDAPException e) 2532 { 2533 Debug.debugException(e); 2534 wrapErr(0, WRAP_COLUMN, e.getMessage()); 2535 return e.getResultCode(); 2536 } 2537 2538 2539 // If appropriate, ensure that any search result entries that include 2540 // base64-encoded attribute values will also include comments that attempt 2541 // to provide a human-readable representation of that value. 2542 final boolean originalCommentAboutBase64EncodedValues = 2543 LDIFWriter.commentAboutBase64EncodedValues(); 2544 LDIFWriter.setCommentAboutBase64EncodedValues( 2545 ! suppressBase64EncodedValueComments.isPresent()); 2546 2547 2548 LDAPConnectionPool pool = null; 2549 try 2550 { 2551 // Create a connection pool that will be used to communicate with the 2552 // directory server. 2553 if (! dryRun.isPresent()) 2554 { 2555 try 2556 { 2557 final StartAdministrativeSessionPostConnectProcessor p; 2558 if (useAdministrativeSession.isPresent()) 2559 { 2560 p = new StartAdministrativeSessionPostConnectProcessor( 2561 new StartAdministrativeSessionExtendedRequest(getToolName(), 2562 true)); 2563 } 2564 else 2565 { 2566 p = null; 2567 } 2568 2569 pool = getConnectionPool(1, 1, 0, p, null, true, 2570 new ReportBindResultLDAPConnectionPoolHealthCheck(this, true, 2571 false)); 2572 } 2573 catch (final LDAPException le) 2574 { 2575 // This shouldn't happen since the pool won't throw an exception if an 2576 // attempt to create an initial connection fails. 2577 Debug.debugException(le); 2578 commentToErr(ERR_LDAPSEARCH_CANNOT_CREATE_CONNECTION_POOL.get( 2579 StaticUtils.getExceptionMessage(le))); 2580 return le.getResultCode(); 2581 } 2582 2583 if (retryFailedOperations.isPresent()) 2584 { 2585 pool.setRetryFailedOperationsDueToInvalidConnections(true); 2586 } 2587 } 2588 2589 2590 // If appropriate, create a rate limiter. 2591 final FixedRateBarrier rateLimiter; 2592 if (ratePerSecond.isPresent()) 2593 { 2594 rateLimiter = new FixedRateBarrier(1000L, ratePerSecond.getValue()); 2595 } 2596 else 2597 { 2598 rateLimiter = null; 2599 } 2600 2601 2602 // If one or more LDAP URL files are provided, then construct search 2603 // requests from those URLs. 2604 if (ldapURLFile.isPresent()) 2605 { 2606 return searchWithLDAPURLs(pool, rateLimiter, searchControls); 2607 } 2608 2609 2610 // Get the set of requested attributes, as a combination of the 2611 // requestedAttribute argument values and any trailing arguments. 2612 final ArrayList<String> attrList = new ArrayList<>(10); 2613 if (requestedAttribute.isPresent()) 2614 { 2615 attrList.addAll(requestedAttribute.getValues()); 2616 } 2617 2618 final List<String> trailingArgs = parser.getTrailingArguments(); 2619 if (! trailingArgs.isEmpty()) 2620 { 2621 final Iterator<String> trailingArgIterator = trailingArgs.iterator(); 2622 if (! (filter.isPresent() || filterFile.isPresent())) 2623 { 2624 trailingArgIterator.next(); 2625 } 2626 2627 while (trailingArgIterator.hasNext()) 2628 { 2629 attrList.add(trailingArgIterator.next()); 2630 } 2631 } 2632 2633 final String[] attributes = new String[attrList.size()]; 2634 attrList.toArray(attributes); 2635 2636 2637 // If either or both the filter or filterFile arguments are provided, then 2638 // use them to get the filters to process. Otherwise, the first trailing 2639 // argument should be a filter. 2640 ResultCode resultCode = ResultCode.SUCCESS; 2641 if (filter.isPresent() || filterFile.isPresent()) 2642 { 2643 if (filter.isPresent()) 2644 { 2645 for (final Filter f : filter.getValues()) 2646 { 2647 final ResultCode rc = searchWithFilter(pool, f, attributes, 2648 rateLimiter, searchControls); 2649 if (rc != ResultCode.SUCCESS) 2650 { 2651 if (resultCode == ResultCode.SUCCESS) 2652 { 2653 resultCode = rc; 2654 } 2655 2656 if (! continueOnError.isPresent()) 2657 { 2658 return resultCode; 2659 } 2660 } 2661 } 2662 } 2663 2664 if (filterFile.isPresent()) 2665 { 2666 final ResultCode rc = searchWithFilterFile(pool, attributes, 2667 rateLimiter, searchControls); 2668 if (rc != ResultCode.SUCCESS) 2669 { 2670 if (resultCode == ResultCode.SUCCESS) 2671 { 2672 resultCode = rc; 2673 } 2674 2675 if (! continueOnError.isPresent()) 2676 { 2677 return resultCode; 2678 } 2679 } 2680 } 2681 } 2682 else 2683 { 2684 final Filter f; 2685 try 2686 { 2687 final String filterStr = 2688 parser.getTrailingArguments().iterator().next(); 2689 f = Filter.create(filterStr); 2690 } 2691 catch (final LDAPException le) 2692 { 2693 // This should never happen. 2694 Debug.debugException(le); 2695 displayResult(le.toLDAPResult()); 2696 return le.getResultCode(); 2697 } 2698 2699 resultCode = 2700 searchWithFilter(pool, f, attributes, rateLimiter, searchControls); 2701 } 2702 2703 return resultCode; 2704 } 2705 finally 2706 { 2707 if (pool != null) 2708 { 2709 try 2710 { 2711 pool.close(); 2712 } 2713 catch (final Exception e) 2714 { 2715 Debug.debugException(e); 2716 } 2717 } 2718 2719 if (outStream != null) 2720 { 2721 try 2722 { 2723 outStream.close(); 2724 outStream = null; 2725 } 2726 catch (final Exception e) 2727 { 2728 Debug.debugException(e); 2729 } 2730 } 2731 2732 if (errStream != null) 2733 { 2734 try 2735 { 2736 errStream.close(); 2737 errStream = null; 2738 } 2739 catch (final Exception e) 2740 { 2741 Debug.debugException(e); 2742 } 2743 } 2744 2745 LDIFWriter.setCommentAboutBase64EncodedValues( 2746 originalCommentAboutBase64EncodedValues); 2747 } 2748 } 2749 2750 2751 2752 /** 2753 * Processes a set of searches using LDAP URLs read from one or more files. 2754 * 2755 * @param pool The connection pool to use to communicate with the 2756 * directory server. 2757 * @param rateLimiter An optional fixed-rate barrier that can be used for 2758 * request rate limiting. 2759 * @param searchControls The set of controls to include in search requests. 2760 * 2761 * @return A result code indicating the result of the processing. 2762 */ 2763 @NotNull() 2764 private ResultCode searchWithLDAPURLs(@NotNull final LDAPConnectionPool pool, 2765 @Nullable final FixedRateBarrier rateLimiter, 2766 @NotNull final List<Control> searchControls) 2767 { 2768 ResultCode resultCode = ResultCode.SUCCESS; 2769 for (final File f : ldapURLFile.getValues()) 2770 { 2771 BufferedReader reader = null; 2772 2773 try 2774 { 2775 reader = new BufferedReader(new FileReader(f)); 2776 while (true) 2777 { 2778 final String line = reader.readLine(); 2779 if (line == null) 2780 { 2781 break; 2782 } 2783 2784 if ((line.length() == 0) || line.startsWith("#")) 2785 { 2786 continue; 2787 } 2788 2789 final LDAPURL url; 2790 try 2791 { 2792 url = new LDAPURL(line); 2793 } 2794 catch (final LDAPException le) 2795 { 2796 Debug.debugException(le); 2797 2798 commentToErr(ERR_LDAPSEARCH_MALFORMED_LDAP_URL.get( 2799 f.getAbsolutePath(), line)); 2800 if (resultCode == ResultCode.SUCCESS) 2801 { 2802 resultCode = le.getResultCode(); 2803 } 2804 2805 if (continueOnError.isPresent()) 2806 { 2807 continue; 2808 } 2809 else 2810 { 2811 return resultCode; 2812 } 2813 } 2814 2815 final SearchRequest searchRequest = new SearchRequest( 2816 new LDAPSearchListener(resultWriter, entryTransformations), 2817 url.getBaseDN().toString(), url.getScope(), derefPolicy, 2818 sizeLimit.getValue(), timeLimitSeconds.getValue(), 2819 typesOnly.isPresent(), url.getFilter(), url.getAttributes()); 2820 final ResultCode rc = 2821 doSearch(pool, searchRequest, rateLimiter, searchControls); 2822 if (rc != ResultCode.SUCCESS) 2823 { 2824 if (resultCode == ResultCode.SUCCESS) 2825 { 2826 resultCode = rc; 2827 } 2828 2829 if (! continueOnError.isPresent()) 2830 { 2831 return resultCode; 2832 } 2833 } 2834 } 2835 } 2836 catch (final IOException ioe) 2837 { 2838 commentToErr(ERR_LDAPSEARCH_CANNOT_READ_LDAP_URL_FILE.get( 2839 f.getAbsolutePath(), StaticUtils.getExceptionMessage(ioe))); 2840 return ResultCode.LOCAL_ERROR; 2841 } 2842 finally 2843 { 2844 if (reader != null) 2845 { 2846 try 2847 { 2848 reader.close(); 2849 } 2850 catch (final Exception e) 2851 { 2852 Debug.debugException(e); 2853 } 2854 } 2855 } 2856 } 2857 2858 return resultCode; 2859 } 2860 2861 2862 2863 /** 2864 * Processes a set of searches using filters read from one or more files. 2865 * 2866 * @param pool The connection pool to use to communicate with the 2867 * directory server. 2868 * @param attributes The set of attributes to request that the server 2869 * include in matching entries. 2870 * @param rateLimiter An optional fixed-rate barrier that can be used for 2871 * request rate limiting. 2872 * @param searchControls The set of controls to include in search requests. 2873 * 2874 * @return A result code indicating the result of the processing. 2875 */ 2876 @NotNull() 2877 private ResultCode searchWithFilterFile( 2878 @NotNull final LDAPConnectionPool pool, 2879 @NotNull final String[] attributes, 2880 @Nullable final FixedRateBarrier rateLimiter, 2881 @NotNull final List<Control> searchControls) 2882 { 2883 ResultCode resultCode = ResultCode.SUCCESS; 2884 for (final File f : filterFile.getValues()) 2885 { 2886 FilterFileReader reader = null; 2887 2888 try 2889 { 2890 reader = new FilterFileReader(f); 2891 while (true) 2892 { 2893 final Filter searchFilter; 2894 try 2895 { 2896 searchFilter = reader.readFilter(); 2897 } 2898 catch (final LDAPException le) 2899 { 2900 Debug.debugException(le); 2901 commentToErr(ERR_LDAPSEARCH_MALFORMED_FILTER.get( 2902 f.getAbsolutePath(), le.getMessage())); 2903 if (resultCode == ResultCode.SUCCESS) 2904 { 2905 resultCode = le.getResultCode(); 2906 } 2907 2908 if (continueOnError.isPresent()) 2909 { 2910 continue; 2911 } 2912 else 2913 { 2914 return resultCode; 2915 } 2916 } 2917 2918 if (searchFilter == null) 2919 { 2920 break; 2921 } 2922 2923 final ResultCode rc = searchWithFilter(pool, searchFilter, attributes, 2924 rateLimiter, searchControls); 2925 if (rc != ResultCode.SUCCESS) 2926 { 2927 if (resultCode == ResultCode.SUCCESS) 2928 { 2929 resultCode = rc; 2930 } 2931 2932 if (! continueOnError.isPresent()) 2933 { 2934 return resultCode; 2935 } 2936 } 2937 } 2938 } 2939 catch (final IOException ioe) 2940 { 2941 Debug.debugException(ioe); 2942 commentToErr(ERR_LDAPSEARCH_CANNOT_READ_FILTER_FILE.get( 2943 f.getAbsolutePath(), StaticUtils.getExceptionMessage(ioe))); 2944 return ResultCode.LOCAL_ERROR; 2945 } 2946 finally 2947 { 2948 if (reader != null) 2949 { 2950 try 2951 { 2952 reader.close(); 2953 } 2954 catch (final Exception e) 2955 { 2956 Debug.debugException(e); 2957 } 2958 } 2959 } 2960 } 2961 2962 return resultCode; 2963 } 2964 2965 2966 2967 /** 2968 * Processes a search using the provided filter. 2969 * 2970 * @param pool The connection pool to use to communicate with the 2971 * directory server. 2972 * @param filter The filter to use for the search. 2973 * @param attributes The set of attributes to request that the server 2974 * include in matching entries. 2975 * @param rateLimiter An optional fixed-rate barrier that can be used for 2976 * request rate limiting. 2977 * @param searchControls The set of controls to include in search requests. 2978 * 2979 * @return A result code indicating the result of the processing. 2980 */ 2981 @NotNull() 2982 private ResultCode searchWithFilter(@NotNull final LDAPConnectionPool pool, 2983 @NotNull final Filter filter, 2984 @NotNull final String[] attributes, 2985 @Nullable final FixedRateBarrier rateLimiter, 2986 @NotNull final List<Control> searchControls) 2987 { 2988 final String baseDNString; 2989 if (baseDN.isPresent()) 2990 { 2991 baseDNString = baseDN.getStringValue(); 2992 } 2993 else 2994 { 2995 baseDNString = ""; 2996 } 2997 2998 final SearchRequest searchRequest = new SearchRequest( 2999 new LDAPSearchListener(resultWriter, entryTransformations), 3000 baseDNString, scope.getValue(), derefPolicy, sizeLimit.getValue(), 3001 timeLimitSeconds.getValue(), typesOnly.isPresent(), filter, 3002 attributes); 3003 return doSearch(pool, searchRequest, rateLimiter, searchControls); 3004 } 3005 3006 3007 3008 /** 3009 * Processes a search with the provided information. 3010 * 3011 * @param pool The connection pool to use to communicate with the 3012 * directory server. 3013 * @param searchRequest The search request to process. 3014 * @param rateLimiter An optional fixed-rate barrier that can be used for 3015 * request rate limiting. 3016 * @param searchControls The set of controls to include in search requests. 3017 * 3018 * @return A result code indicating the result of the processing. 3019 */ 3020 @NotNull() 3021 private ResultCode doSearch(@NotNull final LDAPConnectionPool pool, 3022 @NotNull final SearchRequest searchRequest, 3023 @Nullable final FixedRateBarrier rateLimiter, 3024 @NotNull final List<Control> searchControls) 3025 { 3026 if (separateOutputFilePerSearch.isPresent()) 3027 { 3028 try 3029 { 3030 final String path = outputFile.getValue().getAbsolutePath() + '.' + 3031 outputFileCounter.getAndIncrement(); 3032 3033 OutputStream s = new FileOutputStream(path); 3034 3035 if (encryptOutput.isPresent()) 3036 { 3037 s = new PassphraseEncryptedOutputStream(encryptionPassphrase, s); 3038 } 3039 3040 if (compressOutput.isPresent()) 3041 { 3042 s = new GZIPOutputStream(s); 3043 } 3044 3045 if (teeResultsToStandardOut.isPresent()) 3046 { 3047 outStream = new PrintStream(new TeeOutputStream(s, getOut())); 3048 } 3049 else 3050 { 3051 outStream = new PrintStream(s); 3052 } 3053 resultWriter.updateOutputStream(outStream); 3054 errStream = outStream; 3055 } 3056 catch (final Exception e) 3057 { 3058 Debug.debugException(e); 3059 wrapErr(0, WRAP_COLUMN, ERR_LDAPSEARCH_CANNOT_OPEN_OUTPUT_FILE.get( 3060 outputFile.getValue().getAbsolutePath(), 3061 StaticUtils.getExceptionMessage(e))); 3062 return ResultCode.LOCAL_ERROR; 3063 } 3064 3065 resultWriter.writeHeader(); 3066 } 3067 3068 try 3069 { 3070 if (rateLimiter != null) 3071 { 3072 rateLimiter.await(); 3073 } 3074 3075 3076 ASN1OctetString pagedResultsCookie = null; 3077 boolean multiplePages = false; 3078 long totalEntries = 0; 3079 long totalReferences = 0; 3080 3081 SearchResult searchResult; 3082 try 3083 { 3084 while (true) 3085 { 3086 searchRequest.setControls(searchControls); 3087 if (simplePageSize.isPresent()) 3088 { 3089 searchRequest.addControl(new SimplePagedResultsControl( 3090 simplePageSize.getValue(), pagedResultsCookie)); 3091 } 3092 3093 if (dryRun.isPresent()) 3094 { 3095 searchResult = new SearchResult(-1, ResultCode.SUCCESS, 3096 INFO_LDAPSEARCH_DRY_RUN_REQUEST_NOT_SENT.get( 3097 dryRun.getIdentifierString(), 3098 String.valueOf(searchRequest)), 3099 null, null, 0, 0, null); 3100 break; 3101 } 3102 else 3103 { 3104 if (! terse.isPresent()) 3105 { 3106 if (verbose.isPresent() || persistentSearch.isPresent() || 3107 filterFile.isPresent() || ldapURLFile.isPresent() || 3108 (filter.isPresent() && (filter.getNumOccurrences() > 1))) 3109 { 3110 commentToOut(INFO_LDAPSEARCH_SENDING_SEARCH_REQUEST.get( 3111 String.valueOf(searchRequest))); 3112 } 3113 } 3114 searchResult = pool.search(searchRequest); 3115 } 3116 3117 searchResult = handleJSONEncodedResponseControls(searchResult); 3118 3119 if (searchResult.getEntryCount() > 0) 3120 { 3121 totalEntries += searchResult.getEntryCount(); 3122 } 3123 3124 if (searchResult.getReferenceCount() > 0) 3125 { 3126 totalReferences += searchResult.getReferenceCount(); 3127 } 3128 3129 if (simplePageSize.isPresent()) 3130 { 3131 final SimplePagedResultsControl pagedResultsControl; 3132 try 3133 { 3134 pagedResultsControl = SimplePagedResultsControl.get(searchResult); 3135 if (pagedResultsControl == null) 3136 { 3137 throw new LDAPSearchException(new SearchResult( 3138 searchResult.getMessageID(), ResultCode.CONTROL_NOT_FOUND, 3139 ERR_LDAPSEARCH_MISSING_PAGED_RESULTS_RESPONSE_CONTROL. 3140 get(), 3141 searchResult.getMatchedDN(), 3142 searchResult.getReferralURLs(), 3143 searchResult.getSearchEntries(), 3144 searchResult.getSearchReferences(), 3145 searchResult.getEntryCount(), 3146 searchResult.getReferenceCount(), 3147 searchResult.getResponseControls())); 3148 } 3149 3150 if (pagedResultsControl.moreResultsToReturn()) 3151 { 3152 if (verbose.isPresent()) 3153 { 3154 commentToOut( 3155 INFO_LDAPSEARCH_INTERMEDIATE_PAGED_SEARCH_RESULT.get()); 3156 displayResult(searchResult); 3157 } 3158 3159 multiplePages = true; 3160 pagedResultsCookie = pagedResultsControl.getCookie(); 3161 } 3162 else 3163 { 3164 break; 3165 } 3166 } 3167 catch (final LDAPException le) 3168 { 3169 Debug.debugException(le); 3170 throw new LDAPSearchException(new SearchResult( 3171 searchResult.getMessageID(), ResultCode.CONTROL_NOT_FOUND, 3172 ERR_LDAPSEARCH_CANNOT_DECODE_PAGED_RESULTS_RESPONSE_CONTROL. 3173 get(StaticUtils.getExceptionMessage(le)), 3174 searchResult.getMatchedDN(), searchResult.getReferralURLs(), 3175 searchResult.getSearchEntries(), 3176 searchResult.getSearchReferences(), 3177 searchResult.getEntryCount(), 3178 searchResult.getReferenceCount(), 3179 searchResult.getResponseControls())); 3180 } 3181 } 3182 else 3183 { 3184 break; 3185 } 3186 } 3187 } 3188 catch (final LDAPSearchException lse) 3189 { 3190 Debug.debugException(lse); 3191 searchResult = lse.toLDAPResult(); 3192 3193 if (searchResult.getEntryCount() > 0) 3194 { 3195 totalEntries += searchResult.getEntryCount(); 3196 } 3197 3198 if (searchResult.getReferenceCount() > 0) 3199 { 3200 totalReferences += searchResult.getReferenceCount(); 3201 } 3202 } 3203 3204 if ((searchResult.getResultCode() != ResultCode.SUCCESS) || 3205 (searchResult.getDiagnosticMessage() != null) || 3206 (! terse.isPresent())) 3207 { 3208 displayResult(searchResult); 3209 } 3210 3211 if (multiplePages && (! terse.isPresent())) 3212 { 3213 commentToOut(INFO_LDAPSEARCH_TOTAL_SEARCH_ENTRIES.get(totalEntries)); 3214 3215 if (totalReferences > 0) 3216 { 3217 commentToOut(INFO_LDAPSEARCH_TOTAL_SEARCH_REFERENCES.get( 3218 totalReferences)); 3219 } 3220 } 3221 3222 if (countEntries.isPresent()) 3223 { 3224 return ResultCode.valueOf((int) Math.min(totalEntries, 255)); 3225 } 3226 else if (requireMatch.isPresent() && (totalEntries == 0)) 3227 { 3228 return ResultCode.NO_RESULTS_RETURNED; 3229 } 3230 else 3231 { 3232 return searchResult.getResultCode(); 3233 } 3234 } 3235 finally 3236 { 3237 if (separateOutputFilePerSearch.isPresent()) 3238 { 3239 try 3240 { 3241 outStream.close(); 3242 } 3243 catch (final Exception e) 3244 { 3245 Debug.debugException(e); 3246 } 3247 3248 outStream = null; 3249 errStream = null; 3250 } 3251 } 3252 } 3253 3254 3255 3256 /** 3257 * Retrieves a list of the controls that should be used when processing search 3258 * operations. 3259 * 3260 * @return A list of the controls that should be used when processing search 3261 * operations. 3262 * 3263 * @throws LDAPException If a problem is encountered while generating the 3264 * controls for a search request. 3265 */ 3266 @NotNull() 3267 private List<Control> getSearchControls() 3268 throws LDAPException 3269 { 3270 final ArrayList<Control> controls = new ArrayList<>(10); 3271 3272 if (searchControl.isPresent()) 3273 { 3274 controls.addAll(searchControl.getValues()); 3275 } 3276 3277 if (joinRequestControl != null) 3278 { 3279 controls.add(joinRequestControl); 3280 } 3281 3282 if (matchedValuesRequestControl != null) 3283 { 3284 controls.add(matchedValuesRequestControl); 3285 } 3286 3287 if (matchingEntryCountRequestControl != null) 3288 { 3289 controls.add(matchingEntryCountRequestControl); 3290 } 3291 3292 if (overrideSearchLimitsRequestControl != null) 3293 { 3294 controls.add(overrideSearchLimitsRequestControl); 3295 } 3296 3297 if (persistentSearchRequestControl != null) 3298 { 3299 controls.add(persistentSearchRequestControl); 3300 } 3301 3302 if (sortRequestControl != null) 3303 { 3304 controls.add(sortRequestControl); 3305 } 3306 3307 if (vlvRequestControl != null) 3308 { 3309 controls.add(vlvRequestControl); 3310 } 3311 3312 controls.addAll(routeToBackendSetRequestControls); 3313 3314 if (accessLogField.isPresent()) 3315 { 3316 final Map<String,JSONValue> fields = new LinkedHashMap<>(); 3317 for (final String nameValueStr : accessLogField.getValues()) 3318 { 3319 final int colonPos = nameValueStr.indexOf(':'); 3320 if (colonPos < 0) 3321 { 3322 throw new LDAPException(ResultCode.PARAM_ERROR, 3323 ERR_LDAPSEARCH_ACCESS_LOG_FIELD_NO_COLON.get( 3324 accessLogField.getIdentifierString(), nameValueStr)); 3325 } 3326 3327 final String fieldName = nameValueStr.substring(0, colonPos); 3328 if (fields.containsKey(fieldName)) 3329 { 3330 throw new LDAPException(ResultCode.PARAM_ERROR, 3331 ERR_LDAPSEARCH_ACCESS_LOG_FIELD_DUPLICATE_FIELD.get( 3332 accessLogField.getIdentifierString(), fieldName)); 3333 } 3334 3335 final String valueStr = nameValueStr.substring(colonPos + 1); 3336 if (valueStr.equalsIgnoreCase("true")) 3337 { 3338 fields.put(fieldName, JSONBoolean.TRUE); 3339 } 3340 else if (valueStr.equalsIgnoreCase("false")) 3341 { 3342 fields.put(fieldName, JSONBoolean.FALSE); 3343 } 3344 else 3345 { 3346 try 3347 { 3348 final BigDecimal d = new BigDecimal(valueStr); 3349 fields.put(fieldName, new JSONNumber(d)); 3350 } 3351 catch (final Exception e) 3352 { 3353 Debug.debugException(e); 3354 fields.put(fieldName, new JSONString(valueStr)); 3355 } 3356 } 3357 } 3358 3359 controls.add(new AccessLogFieldRequestControl(false, 3360 new JSONObject(fields))); 3361 } 3362 3363 if (accountUsable.isPresent()) 3364 { 3365 controls.add(new AccountUsableRequestControl(true)); 3366 } 3367 3368 if (getBackendSetID.isPresent()) 3369 { 3370 controls.add(new GetBackendSetIDRequestControl(false)); 3371 } 3372 3373 if (getServerID.isPresent()) 3374 { 3375 controls.add(new GetServerIDRequestControl(false)); 3376 } 3377 3378 if (includeReplicationConflictEntries.isPresent()) 3379 { 3380 controls.add(new ReturnConflictEntriesRequestControl(true)); 3381 } 3382 3383 if (includeSoftDeletedEntries.isPresent()) 3384 { 3385 final String valueStr = 3386 StaticUtils.toLowerCase(includeSoftDeletedEntries.getValue()); 3387 if (valueStr.equals("with-non-deleted-entries")) 3388 { 3389 controls.add(new SoftDeletedEntryAccessRequestControl(true, true, 3390 false)); 3391 } 3392 else if (valueStr.equals("without-non-deleted-entries")) 3393 { 3394 controls.add(new SoftDeletedEntryAccessRequestControl(true, false, 3395 false)); 3396 } 3397 else 3398 { 3399 controls.add(new SoftDeletedEntryAccessRequestControl(true, false, 3400 true)); 3401 } 3402 } 3403 3404 if (draftLDUPSubentries.isPresent()) 3405 { 3406 controls.add(new DraftLDUPSubentriesRequestControl(true)); 3407 } 3408 3409 if (rfc3672Subentries.isPresent()) 3410 { 3411 controls.add(new RFC3672SubentriesRequestControl( 3412 rfc3672Subentries.getValue())); 3413 } 3414 3415 if (manageDsaIT.isPresent()) 3416 { 3417 controls.add(new ManageDsaITRequestControl(true)); 3418 } 3419 3420 if (realAttributesOnly.isPresent()) 3421 { 3422 controls.add(new RealAttributesOnlyRequestControl(true)); 3423 } 3424 3425 if (routeToServer.isPresent()) 3426 { 3427 controls.add(new RouteToServerRequestControl(false, 3428 routeToServer.getValue(), false, false, false)); 3429 } 3430 3431 if (virtualAttributesOnly.isPresent()) 3432 { 3433 controls.add(new VirtualAttributesOnlyRequestControl(true)); 3434 } 3435 3436 if (excludeBranch.isPresent()) 3437 { 3438 final ArrayList<String> dns = 3439 new ArrayList<>(excludeBranch.getValues().size()); 3440 for (final DN dn : excludeBranch.getValues()) 3441 { 3442 dns.add(dn.toString()); 3443 } 3444 controls.add(new ExcludeBranchRequestControl(true, dns)); 3445 } 3446 3447 if (assertionFilter.isPresent()) 3448 { 3449 controls.add(new AssertionRequestControl( 3450 assertionFilter.getValue(), true)); 3451 } 3452 3453 if (getEffectiveRightsAuthzID.isPresent()) 3454 { 3455 final String[] attributes; 3456 if (getEffectiveRightsAttribute.isPresent()) 3457 { 3458 attributes = new String[getEffectiveRightsAttribute.getValues().size()]; 3459 for (int i=0; i < attributes.length; i++) 3460 { 3461 attributes[i] = getEffectiveRightsAttribute.getValues().get(i); 3462 } 3463 } 3464 else 3465 { 3466 attributes = StaticUtils.NO_STRINGS; 3467 } 3468 3469 controls.add(new GetEffectiveRightsRequestControl(true, 3470 getEffectiveRightsAuthzID.getValue(), attributes)); 3471 } 3472 3473 if (operationPurpose.isPresent()) 3474 { 3475 controls.add(new OperationPurposeRequestControl(true, "ldapsearch", 3476 Version.NUMERIC_VERSION_STRING, "LDAPSearch.getSearchControls", 3477 operationPurpose.getValue())); 3478 } 3479 3480 if (proxyAs.isPresent()) 3481 { 3482 final String proxyAsValue = proxyAs.getValue(); 3483 final String lowerProxyAsValue = StaticUtils.toLowerCase(proxyAsValue); 3484 if (lowerProxyAsValue.startsWith("dn:")) 3485 { 3486 final String dnString = proxyAsValue.substring(3); 3487 if (! DN.isValidDN(dnString)) 3488 { 3489 throw new LDAPException(ResultCode.PARAM_ERROR, 3490 ERR_LDAPSEARCH_PROXY_AS_DN_NOT_DN.get( 3491 proxyAs.getIdentifierString(), dnString)); 3492 } 3493 } 3494 else if (! lowerProxyAsValue.startsWith("u:")) 3495 { 3496 throw new LDAPException(ResultCode.PARAM_ERROR, 3497 ERR_LDAPSEARCH_PROXY_AS_VALUE_MISSING_PREFIX.get( 3498 proxyAs.getIdentifierString())); 3499 } 3500 3501 controls.add(new ProxiedAuthorizationV2RequestControl(proxyAsValue)); 3502 } 3503 3504 if (proxyV1As.isPresent()) 3505 { 3506 controls.add(new ProxiedAuthorizationV1RequestControl( 3507 proxyV1As.getValue())); 3508 } 3509 3510 if (suppressOperationalAttributeUpdates.isPresent()) 3511 { 3512 final EnumSet<SuppressType> suppressTypes = 3513 EnumSet.noneOf(SuppressType.class); 3514 for (final String s : suppressOperationalAttributeUpdates.getValues()) 3515 { 3516 if (s.equalsIgnoreCase("last-access-time")) 3517 { 3518 suppressTypes.add(SuppressType.LAST_ACCESS_TIME); 3519 } 3520 else if (s.equalsIgnoreCase("last-login-time")) 3521 { 3522 suppressTypes.add(SuppressType.LAST_LOGIN_TIME); 3523 } 3524 else if (s.equalsIgnoreCase("last-login-ip")) 3525 { 3526 suppressTypes.add(SuppressType.LAST_LOGIN_IP); 3527 } 3528 } 3529 3530 controls.add(new SuppressOperationalAttributeUpdateRequestControl( 3531 suppressTypes)); 3532 } 3533 3534 if (rejectUnindexedSearch.isPresent()) 3535 { 3536 controls.add(new RejectUnindexedSearchRequestControl()); 3537 } 3538 3539 if (permitUnindexedSearch.isPresent()) 3540 { 3541 controls.add(new PermitUnindexedSearchRequestControl()); 3542 } 3543 3544 if (useJSONFormattedRequestControls.isPresent()) 3545 { 3546 final JSONFormattedRequestControl jsonFormattedRequestControl = 3547 JSONFormattedRequestControl.createWithControls(true, controls); 3548 controls.clear(); 3549 controls.add(jsonFormattedRequestControl); 3550 } 3551 3552 return controls; 3553 } 3554 3555 3556 3557 /** 3558 * Displays information about the provided result, including special 3559 * processing for a number of supported response controls. 3560 * 3561 * @param result The result to examine. 3562 */ 3563 private void displayResult(@NotNull final LDAPResult result) 3564 { 3565 resultWriter.writeResult(result); 3566 } 3567 3568 3569 3570 /** 3571 * Writes the provided message to the output stream. 3572 * 3573 * @param message The message to be written. 3574 */ 3575 void writeOut(@NotNull final String message) 3576 { 3577 if (outStream == null) 3578 { 3579 out(message); 3580 } 3581 else 3582 { 3583 outStream.println(message); 3584 } 3585 } 3586 3587 3588 3589 /** 3590 * Writes the provided message to the error stream. 3591 * 3592 * @param message The message to be written. 3593 */ 3594 private void writeErr(@NotNull final String message) 3595 { 3596 if (errStream == null) 3597 { 3598 err(message); 3599 } 3600 else 3601 { 3602 errStream.println(message); 3603 } 3604 } 3605 3606 3607 3608 /** 3609 * Writes a line-wrapped, commented version of the provided message to 3610 * standard output. 3611 * 3612 * @param message The message to be written. 3613 */ 3614 private void commentToOut(@NotNull final String message) 3615 { 3616 if (terse.isPresent()) 3617 { 3618 return; 3619 } 3620 3621 for (final String line : StaticUtils.wrapLine(message, (WRAP_COLUMN - 2))) 3622 { 3623 writeOut("# " + line); 3624 } 3625 } 3626 3627 3628 3629 /** 3630 * Writes a line-wrapped, commented version of the provided message to 3631 * standard error. 3632 * 3633 * @param message The message to be written. 3634 */ 3635 private void commentToErr(@NotNull final String message) 3636 { 3637 for (final String line : StaticUtils.wrapLine(message, (WRAP_COLUMN - 2))) 3638 { 3639 writeErr("# " + line); 3640 } 3641 } 3642 3643 3644 3645 /** 3646 * Retrieves the tool's output stream. 3647 * 3648 * @return The tool's output stream. 3649 */ 3650 @NotNull() 3651 PrintStream getOutStream() 3652 { 3653 if (outStream == null) 3654 { 3655 return getOut(); 3656 } 3657 else 3658 { 3659 return outStream; 3660 } 3661 } 3662 3663 3664 3665 /** 3666 * Retrieves the tool's error stream. 3667 * 3668 * @return The tool's error stream. 3669 */ 3670 @NotNull() 3671 PrintStream getErrStream() 3672 { 3673 if (errStream == null) 3674 { 3675 return getErr(); 3676 } 3677 else 3678 { 3679 return errStream; 3680 } 3681 } 3682 3683 3684 3685 /** 3686 * Sets the output handler that should be used by this tool This is primarily 3687 * intended for testing purposes. 3688 * 3689 * @param resultWriter The result writer that should be used by this tool. 3690 */ 3691 void setResultWriter(@NotNull final LDAPResultWriter resultWriter) 3692 { 3693 this.resultWriter = resultWriter; 3694 } 3695 3696 3697 3698 /** 3699 * {@inheritDoc} 3700 */ 3701 @Override() 3702 public void handleUnsolicitedNotification( 3703 @NotNull final LDAPConnection connection, 3704 @NotNull final ExtendedResult notification) 3705 { 3706 resultWriter.writeUnsolicitedNotification(connection, notification); 3707 } 3708 3709 3710 3711 /** 3712 * Examines the provided search result to see if it includes a JSONf-formatted 3713 * response control. If so, then its embedded controls will be extracted and 3714 * a new search result will be returned with those extracted controls instead 3715 * of the JSON-formatted response control. Otherwise, the provided search 3716 * result will be returned. 3717 * 3718 * @param searchResult The search result to be handled. It must not be 3719 * {@code null}. 3720 * 3721 * @return A new search result with the controls extracted from a 3722 * JSON-formatted response control, or the original search result if 3723 * it did not include a JSON-formatted response control. 3724 */ 3725 @NotNull() 3726 static SearchResult handleJSONEncodedResponseControls( 3727 @NotNull final SearchResult searchResult) 3728 { 3729 try 3730 { 3731 final JSONFormattedResponseControl jsonFormattedResponseControl = 3732 JSONFormattedResponseControl.get(searchResult); 3733 if (jsonFormattedResponseControl == null) 3734 { 3735 return searchResult; 3736 } 3737 3738 final JSONFormattedControlDecodeBehavior decodeBehavior = 3739 new JSONFormattedControlDecodeBehavior(); 3740 decodeBehavior.setThrowOnUnparsableObject(false); 3741 decodeBehavior.setThrowOnInvalidCriticalControl(false); 3742 decodeBehavior.setThrowOnInvalidNonCriticalControl(false); 3743 decodeBehavior.setThrowOnInvalidNonCriticalControl(false); 3744 decodeBehavior.setAllowEmbeddedJSONFormattedControl(true); 3745 decodeBehavior.setStrict(false); 3746 3747 final List<Control> decodedControls = 3748 jsonFormattedResponseControl.decodeEmbeddedControls( 3749 decodeBehavior, null); 3750 3751 return new SearchResult(searchResult.getMessageID(), 3752 searchResult.getResultCode(), 3753 searchResult.getDiagnosticMessage(), 3754 searchResult.getMatchedDN(), 3755 searchResult.getReferralURLs(), 3756 searchResult.getSearchEntries(), 3757 searchResult.getSearchReferences(), 3758 searchResult.getEntryCount(), 3759 searchResult.getReferenceCount(), 3760 StaticUtils.toArray(decodedControls, Control.class)); 3761 } 3762 catch (final LDAPException e) 3763 { 3764 Debug.debugException(e); 3765 return searchResult; 3766 } 3767 } 3768 3769 3770 3771 /** 3772 * {@inheritDoc} 3773 */ 3774 @Override() 3775 @NotNull() 3776 public LinkedHashMap<String[],String> getExampleUsages() 3777 { 3778 final LinkedHashMap<String[],String> examples = 3779 new LinkedHashMap<>(StaticUtils.computeMapCapacity(5)); 3780 3781 String[] args = 3782 { 3783 "--hostname", "directory.example.com", 3784 "--port", "389", 3785 "--bindDN", "uid=jdoe,ou=People,dc=example,dc=com", 3786 "--bindPassword", "password", 3787 "--baseDN", "ou=People,dc=example,dc=com", 3788 "--scope", "sub", 3789 "(uid=jqpublic)", 3790 "givenName", 3791 "sn", 3792 "mail" 3793 }; 3794 examples.put(args, INFO_LDAPSEARCH_EXAMPLE_1.get()); 3795 3796 3797 args = new String[] 3798 { 3799 "--hostname", "directory.example.com", 3800 "--port", "636", 3801 "--useSSL", 3802 "--saslOption", "mech=PLAIN", 3803 "--saslOption", "authID=u:jdoe", 3804 "--bindPasswordFile", "/path/to/password/file", 3805 "--baseDN", "ou=People,dc=example,dc=com", 3806 "--scope", "sub", 3807 "--filterFile", "/path/to/filter/file", 3808 "--outputFile", "/path/to/base/output/file", 3809 "--separateOutputFilePerSearch", 3810 "--requestedAttribute", "*", 3811 "--requestedAttribute", "+" 3812 }; 3813 examples.put(args, INFO_LDAPSEARCH_EXAMPLE_2.get()); 3814 3815 3816 args = new String[] 3817 { 3818 "--hostname", "directory.example.com", 3819 "--port", "389", 3820 "--useStartTLS", 3821 "--trustStorePath", "/path/to/truststore/file", 3822 "--baseDN", "", 3823 "--scope", "base", 3824 "--outputFile", "/path/to/output/file", 3825 "--teeResultsToStandardOut", 3826 "(objectClass=*)", 3827 "*", 3828 "+" 3829 }; 3830 examples.put(args, INFO_LDAPSEARCH_EXAMPLE_3.get()); 3831 3832 3833 args = new String[] 3834 { 3835 "--hostname", "directory.example.com", 3836 "--port", "389", 3837 "--bindDN", "uid=admin,dc=example,dc=com", 3838 "--baseDN", "dc=example,dc=com", 3839 "--scope", "sub", 3840 "--outputFile", "/path/to/output/file", 3841 "--simplePageSize", "100", 3842 "(objectClass=*)", 3843 "*", 3844 "+" 3845 }; 3846 examples.put(args, INFO_LDAPSEARCH_EXAMPLE_4.get()); 3847 3848 3849 args = new String[] 3850 { 3851 "--hostname", "directory.example.com", 3852 "--port", "389", 3853 "--bindDN", "uid=admin,dc=example,dc=com", 3854 "--baseDN", "dc=example,dc=com", 3855 "--scope", "sub", 3856 "(&(givenName=John)(sn=Doe))", 3857 "debugsearchindex" 3858 }; 3859 examples.put(args, INFO_LDAPSEARCH_EXAMPLE_5.get()); 3860 3861 return examples; 3862 } 3863}