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