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