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