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