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, but one of 1062 // them must be present. 1063 parser.addExclusiveArgumentSet(baseDN, ldapURLFile); 1064 parser.addRequiredArgumentSet(baseDN, ldapURLFile); 1065 1066 // The scope and ldapURLFile arguments can't be used together. 1067 parser.addExclusiveArgumentSet(scope, ldapURLFile); 1068 1069 // The requestedAttribute and ldapURLFile arguments can't be used together. 1070 parser.addExclusiveArgumentSet(requestedAttribute, ldapURLFile); 1071 1072 // The filter and ldapURLFile arguments can't be used together. 1073 parser.addExclusiveArgumentSet(filter, ldapURLFile); 1074 1075 // The filterFile and ldapURLFile arguments can't be used together. 1076 parser.addExclusiveArgumentSet(filterFile, ldapURLFile); 1077 1078 // The followReferrals and manageDsaIT arguments can't be used together. 1079 parser.addExclusiveArgumentSet(followReferrals, manageDsaIT); 1080 1081 // The persistent search argument can't be used with either the filterFile 1082 // or ldapURLFile arguments. 1083 parser.addExclusiveArgumentSet(persistentSearch, filterFile); 1084 parser.addExclusiveArgumentSet(persistentSearch, ldapURLFile); 1085 1086 // The realAttributesOnly and virtualAttributesOnly arguments can't be used 1087 // together. 1088 parser.addExclusiveArgumentSet(realAttributesOnly, virtualAttributesOnly); 1089 1090 // The simplePageSize and virtualListView arguments can't be used together. 1091 parser.addExclusiveArgumentSet(simplePageSize, virtualListView); 1092 1093 // The terse and verbose arguments can't be used together. 1094 parser.addExclusiveArgumentSet(terse, verbose); 1095 1096 // The getEffectiveRightsAttribute argument requires the 1097 // getEffectiveRightsAuthzID argument. 1098 parser.addDependentArgumentSet(getEffectiveRightsAttribute, 1099 getEffectiveRightsAuthzID); 1100 1101 // The virtualListView argument requires the sortOrder argument. 1102 parser.addDependentArgumentSet(virtualListView, sortOrder); 1103 1104 // The separateOutputFilePerSearch argument requires the outputFile 1105 // argument. It also requires either the filter, filterFile or ldapURLFile 1106 // argument. 1107 parser.addDependentArgumentSet(separateOutputFilePerSearch, outputFile); 1108 parser.addDependentArgumentSet(separateOutputFilePerSearch, filter, 1109 filterFile, ldapURLFile); 1110 1111 // The teeResultsToStandardOut argument requires the outputFile argument. 1112 parser.addDependentArgumentSet(teeResultsToStandardOut, outputFile); 1113 1114 // The wrapColumn and dontWrap arguments must not be used together. 1115 parser.addExclusiveArgumentSet(wrapColumn, dontWrap); 1116 1117 // All arguments that specifically pertain to join processing can only be 1118 // used if the joinRule argument is provided. 1119 parser.addDependentArgumentSet(joinBaseDN, joinRule); 1120 parser.addDependentArgumentSet(joinScope, joinRule); 1121 parser.addDependentArgumentSet(joinSizeLimit, joinRule); 1122 parser.addDependentArgumentSet(joinFilter, joinRule); 1123 parser.addDependentArgumentSet(joinRequestedAttribute, joinRule); 1124 parser.addDependentArgumentSet(joinRequireMatch, joinRule); 1125 1126 // The countEntries argument must not be used in conjunction with the 1127 // filter, filterFile, LDAPURLFile, or persistentSearch arguments. 1128 parser.addExclusiveArgumentSet(countEntries, filter); 1129 parser.addExclusiveArgumentSet(countEntries, filterFile); 1130 parser.addExclusiveArgumentSet(countEntries, ldapURLFile); 1131 parser.addExclusiveArgumentSet(countEntries, persistentSearch); 1132 1133 1134 // The hideRedactedValueCount argument requires the redactAttribute 1135 // argument. 1136 parser.addDependentArgumentSet(hideRedactedValueCount, redactAttribute); 1137 1138 // The scrambleJSONField and scrambleRandomSeed arguments require the 1139 // scrambleAttribute argument. 1140 parser.addDependentArgumentSet(scrambleJSONField, scrambleAttribute); 1141 parser.addDependentArgumentSet(scrambleRandomSeed, scrambleAttribute); 1142 1143 // The renameAttributeFrom and renameAttributeTo arguments must be provided 1144 // together. 1145 parser.addDependentArgumentSet(renameAttributeFrom, renameAttributeTo); 1146 parser.addDependentArgumentSet(renameAttributeTo, renameAttributeFrom); 1147 1148 // The moveSubtreeFrom and moveSubtreeTo arguments must be provided 1149 // together. 1150 parser.addDependentArgumentSet(moveSubtreeFrom, moveSubtreeTo); 1151 parser.addDependentArgumentSet(moveSubtreeTo, moveSubtreeFrom); 1152 } 1153 1154 1155 1156 /** 1157 * {@inheritDoc} 1158 */ 1159 @Override() 1160 protected List<Control> getBindControls() 1161 { 1162 final ArrayList<Control> bindControls = new ArrayList<Control>(10); 1163 1164 if (bindControl.isPresent()) 1165 { 1166 bindControls.addAll(bindControl.getValues()); 1167 } 1168 1169 if (authorizationIdentity.isPresent()) 1170 { 1171 bindControls.add(new AuthorizationIdentityRequestControl(false)); 1172 } 1173 1174 if (getAuthorizationEntryAttribute.isPresent()) 1175 { 1176 bindControls.add(new GetAuthorizationEntryRequestControl(true, true, 1177 getAuthorizationEntryAttribute.getValues())); 1178 } 1179 1180 if (getUserResourceLimits.isPresent()) 1181 { 1182 bindControls.add(new GetUserResourceLimitsRequestControl()); 1183 } 1184 1185 if (usePasswordPolicyControl.isPresent()) 1186 { 1187 bindControls.add(new PasswordPolicyRequestControl()); 1188 } 1189 1190 if (suppressOperationalAttributeUpdates.isPresent()) 1191 { 1192 final EnumSet<SuppressType> suppressTypes = 1193 EnumSet.noneOf(SuppressType.class); 1194 for (final String s : suppressOperationalAttributeUpdates.getValues()) 1195 { 1196 if (s.equalsIgnoreCase("last-access-time")) 1197 { 1198 suppressTypes.add(SuppressType.LAST_ACCESS_TIME); 1199 } 1200 else if (s.equalsIgnoreCase("last-login-time")) 1201 { 1202 suppressTypes.add(SuppressType.LAST_LOGIN_TIME); 1203 } 1204 else if (s.equalsIgnoreCase("last-login-ip")) 1205 { 1206 suppressTypes.add(SuppressType.LAST_LOGIN_IP); 1207 } 1208 } 1209 1210 bindControls.add(new SuppressOperationalAttributeUpdateRequestControl( 1211 suppressTypes)); 1212 } 1213 1214 return bindControls; 1215 } 1216 1217 1218 1219 /** 1220 * {@inheritDoc} 1221 */ 1222 @Override() 1223 protected boolean supportsMultipleServers() 1224 { 1225 // We will support providing information about multiple servers. This tool 1226 // will not communicate with multiple servers concurrently, but it can 1227 // accept information about multiple servers in the event that multiple 1228 // searches are to be performed and a server goes down in the middle of 1229 // those searches. In this case, we can resume processing on a 1230 // newly-created connection, possibly to a different server. 1231 return true; 1232 } 1233 1234 1235 1236 /** 1237 * {@inheritDoc} 1238 */ 1239 @Override() 1240 public void doExtendedNonLDAPArgumentValidation() 1241 throws ArgumentException 1242 { 1243 // If wrapColumn was provided, then use its value. Otherwise, if dontWrap 1244 // was provided, then use that. 1245 if (wrapColumn.isPresent()) 1246 { 1247 final int wc = wrapColumn.getValue(); 1248 if (wc <= 0) 1249 { 1250 WRAP_COLUMN = Integer.MAX_VALUE; 1251 } 1252 else 1253 { 1254 WRAP_COLUMN = wc; 1255 } 1256 } 1257 else if (dontWrap.isPresent()) 1258 { 1259 WRAP_COLUMN = Integer.MAX_VALUE; 1260 } 1261 1262 1263 // If the ldapURLFile argument was provided, then there must not be any 1264 // trailing arguments. 1265 final List<String> trailingArgs = parser.getTrailingArguments(); 1266 if (ldapURLFile.isPresent()) 1267 { 1268 if (! trailingArgs.isEmpty()) 1269 { 1270 throw new ArgumentException( 1271 ERR_LDAPSEARCH_TRAILING_ARGS_WITH_URL_FILE.get( 1272 ldapURLFile.getIdentifierString())); 1273 } 1274 } 1275 1276 1277 // If the filter or filterFile argument was provided, then there may 1278 // optionally be trailing arguments, but the first trailing argument must 1279 // not be a filter. 1280 if (filter.isPresent() || filterFile.isPresent()) 1281 { 1282 if (! trailingArgs.isEmpty()) 1283 { 1284 try 1285 { 1286 Filter.create(trailingArgs.get(0)); 1287 throw new ArgumentException( 1288 ERR_LDAPSEARCH_TRAILING_FILTER_WITH_FILTER_FILE.get( 1289 filterFile.getIdentifierString())); 1290 } 1291 catch (final LDAPException le) 1292 { 1293 // This is the normal condition. Not even worth debugging the 1294 // exception. 1295 } 1296 } 1297 } 1298 1299 1300 // If none of the ldapURLFile, filter, or filterFile arguments was provided, 1301 // then there must be at least one trailing argument, and the first trailing 1302 // argument must be a valid search filter. 1303 if (! (ldapURLFile.isPresent() || filter.isPresent() || 1304 filterFile.isPresent())) 1305 { 1306 if (trailingArgs.isEmpty()) 1307 { 1308 throw new ArgumentException(ERR_LDAPSEARCH_NO_TRAILING_ARGS.get( 1309 filterFile.getIdentifierString(), 1310 ldapURLFile.getIdentifierString())); 1311 } 1312 1313 try 1314 { 1315 Filter.create(trailingArgs.get(0)); 1316 } 1317 catch (final Exception e) 1318 { 1319 Debug.debugException(e); 1320 throw new ArgumentException( 1321 ERR_LDAPSEARCH_FIRST_TRAILING_ARG_NOT_FILTER.get( 1322 trailingArgs.get(0)), 1323 e); 1324 } 1325 } 1326 1327 1328 // There should never be a case in which a trailing argument starts with a 1329 // dash, and it's probably an attempt to use a named argument but that was 1330 // inadvertently put after the filter. Warn about the problem, but don't 1331 // fail. 1332 for (final String s : trailingArgs) 1333 { 1334 if (s.startsWith("-")) 1335 { 1336 commentToErr(WARN_LDAPSEARCH_TRAILING_ARG_STARTS_WITH_DASH.get(s)); 1337 break; 1338 } 1339 } 1340 1341 1342 // If any matched values filters are specified, then validate them and 1343 // pre-create the matched values request control. 1344 if (matchedValuesFilter.isPresent()) 1345 { 1346 final List<Filter> filterList = matchedValuesFilter.getValues(); 1347 final MatchedValuesFilter[] matchedValuesFilters = 1348 new MatchedValuesFilter[filterList.size()]; 1349 for (int i=0; i < matchedValuesFilters.length; i++) 1350 { 1351 try 1352 { 1353 matchedValuesFilters[i] = 1354 MatchedValuesFilter.create(filterList.get(i)); 1355 } 1356 catch (final Exception e) 1357 { 1358 Debug.debugException(e); 1359 throw new ArgumentException( 1360 ERR_LDAPSEARCH_INVALID_MATCHED_VALUES_FILTER.get( 1361 filterList.get(i).toString()), 1362 e); 1363 } 1364 } 1365 1366 matchedValuesRequestControl = 1367 new MatchedValuesRequestControl(true, matchedValuesFilters); 1368 } 1369 1370 1371 // If we should use the matching entry count request control, then validate 1372 // the argument value and pre-create the control. 1373 if (matchingEntryCountControl.isPresent()) 1374 { 1375 boolean allowUnindexed = false; 1376 boolean alwaysExamine = false; 1377 boolean debug = false; 1378 boolean skipResolvingExplodedIndexes = false; 1379 Integer examineCount = null; 1380 Long fastShortCircuitThreshold = null; 1381 Long slowShortCircuitThreshold = null; 1382 1383 try 1384 { 1385 for (final String element : 1386 matchingEntryCountControl.getValue().toLowerCase().split(":")) 1387 { 1388 if (element.startsWith("examinecount=")) 1389 { 1390 examineCount = Integer.parseInt(element.substring(13)); 1391 } 1392 else if (element.equals("allowunindexed")) 1393 { 1394 allowUnindexed = true; 1395 } 1396 else if (element.equals("alwaysexamine")) 1397 { 1398 alwaysExamine = true; 1399 } 1400 else if (element.equals("skipresolvingexplodedindexes")) 1401 { 1402 skipResolvingExplodedIndexes = true; 1403 } 1404 else if (element.startsWith("fastshortcircuitthreshold=")) 1405 { 1406 fastShortCircuitThreshold = Long.parseLong(element.substring(26)); 1407 } 1408 else if (element.startsWith("slowshortcircuitthreshold=")) 1409 { 1410 slowShortCircuitThreshold = Long.parseLong(element.substring(26)); 1411 } 1412 else if (element.equals("debug")) 1413 { 1414 debug = true; 1415 } 1416 else 1417 { 1418 throw new ArgumentException( 1419 ERR_LDAPSEARCH_MATCHING_ENTRY_COUNT_INVALID_VALUE.get( 1420 matchingEntryCountControl.getIdentifierString())); 1421 } 1422 } 1423 } 1424 catch (final ArgumentException ae) 1425 { 1426 Debug.debugException(ae); 1427 throw ae; 1428 } 1429 catch (final Exception e) 1430 { 1431 Debug.debugException(e); 1432 throw new ArgumentException( 1433 ERR_LDAPSEARCH_MATCHING_ENTRY_COUNT_INVALID_VALUE.get( 1434 matchingEntryCountControl.getIdentifierString()), 1435 e); 1436 } 1437 1438 if (examineCount == null) 1439 { 1440 throw new ArgumentException( 1441 ERR_LDAPSEARCH_MATCHING_ENTRY_COUNT_INVALID_VALUE.get( 1442 matchingEntryCountControl.getIdentifierString())); 1443 } 1444 1445 matchingEntryCountRequestControl = new MatchingEntryCountRequestControl( 1446 true, examineCount, alwaysExamine, allowUnindexed, 1447 skipResolvingExplodedIndexes, fastShortCircuitThreshold, 1448 slowShortCircuitThreshold, debug); 1449 } 1450 1451 1452 // If we should use the persistent search request control, then validate 1453 // the argument value and pre-create the control. 1454 if (persistentSearch.isPresent()) 1455 { 1456 boolean changesOnly = true; 1457 boolean returnECs = true; 1458 EnumSet<PersistentSearchChangeType> changeTypes = 1459 EnumSet.allOf(PersistentSearchChangeType.class); 1460 try 1461 { 1462 final String[] elements = 1463 persistentSearch.getValue().toLowerCase().split(":"); 1464 if (elements.length == 0) 1465 { 1466 throw new ArgumentException( 1467 ERR_LDAPSEARCH_PERSISTENT_SEARCH_INVALID_VALUE.get( 1468 persistentSearch.getIdentifierString())); 1469 } 1470 1471 final String header = StaticUtils.toLowerCase(elements[0]); 1472 if (! (header.equals("ps") || header.equals("persist") || 1473 header.equals("persistent") || header.equals("psearch") || 1474 header.equals("persistentsearch"))) 1475 { 1476 throw new ArgumentException( 1477 ERR_LDAPSEARCH_PERSISTENT_SEARCH_INVALID_VALUE.get( 1478 persistentSearch.getIdentifierString())); 1479 } 1480 1481 if (elements.length > 1) 1482 { 1483 final String ctString = StaticUtils.toLowerCase(elements[1]); 1484 if (ctString.equals("any")) 1485 { 1486 changeTypes = EnumSet.allOf(PersistentSearchChangeType.class); 1487 } 1488 else 1489 { 1490 changeTypes.clear(); 1491 for (final String t : ctString.split(",")) 1492 { 1493 if (t.equals("add")) 1494 { 1495 changeTypes.add(PersistentSearchChangeType.ADD); 1496 } 1497 else if (t.equals("del") || t.equals("delete")) 1498 { 1499 changeTypes.add(PersistentSearchChangeType.DELETE); 1500 } 1501 else if (t.equals("mod") || t.equals("modify")) 1502 { 1503 changeTypes.add(PersistentSearchChangeType.MODIFY); 1504 } 1505 else if (t.equals("moddn") || t.equals("modrdn") || 1506 t.equals("modifydn") || t.equals("modifyrdn")) 1507 { 1508 changeTypes.add(PersistentSearchChangeType.MODIFY_DN); 1509 } 1510 else 1511 { 1512 throw new ArgumentException( 1513 ERR_LDAPSEARCH_PERSISTENT_SEARCH_INVALID_VALUE.get( 1514 persistentSearch.getIdentifierString())); 1515 } 1516 } 1517 } 1518 } 1519 1520 if (elements.length > 2) 1521 { 1522 if (elements[2].equalsIgnoreCase("true") || elements[2].equals("1")) 1523 { 1524 changesOnly = true; 1525 } 1526 else if (elements[2].equalsIgnoreCase("false") || 1527 elements[2].equals("0")) 1528 { 1529 changesOnly = false; 1530 } 1531 else 1532 { 1533 throw new ArgumentException( 1534 ERR_LDAPSEARCH_PERSISTENT_SEARCH_INVALID_VALUE.get( 1535 persistentSearch.getIdentifierString())); 1536 } 1537 } 1538 1539 if (elements.length > 3) 1540 { 1541 if (elements[3].equalsIgnoreCase("true") || elements[3].equals("1")) 1542 { 1543 returnECs = true; 1544 } 1545 else if (elements[3].equalsIgnoreCase("false") || 1546 elements[3].equals("0")) 1547 { 1548 returnECs = false; 1549 } 1550 else 1551 { 1552 throw new ArgumentException( 1553 ERR_LDAPSEARCH_PERSISTENT_SEARCH_INVALID_VALUE.get( 1554 persistentSearch.getIdentifierString())); 1555 } 1556 } 1557 } 1558 catch (final ArgumentException ae) 1559 { 1560 Debug.debugException(ae); 1561 throw ae; 1562 } 1563 catch (final Exception e) 1564 { 1565 Debug.debugException(e); 1566 throw new ArgumentException( 1567 ERR_LDAPSEARCH_PERSISTENT_SEARCH_INVALID_VALUE.get( 1568 persistentSearch.getIdentifierString()), 1569 e); 1570 } 1571 1572 persistentSearchRequestControl = new PersistentSearchRequestControl( 1573 changeTypes, changesOnly, returnECs, true); 1574 } 1575 1576 1577 // If we should use the server-side sort request control, then validate the 1578 // sort order and pre-create the control. 1579 if (sortOrder.isPresent()) 1580 { 1581 final ArrayList<SortKey> sortKeyList = new ArrayList<SortKey>(5); 1582 final StringTokenizer tokenizer = 1583 new StringTokenizer(sortOrder.getValue(), ", "); 1584 while (tokenizer.hasMoreTokens()) 1585 { 1586 final String token = tokenizer.nextToken(); 1587 1588 final boolean ascending; 1589 String attributeName; 1590 if (token.startsWith("-")) 1591 { 1592 ascending = false; 1593 attributeName = token.substring(1); 1594 } 1595 else if (token.startsWith("+")) 1596 { 1597 ascending = true; 1598 attributeName = token.substring(1); 1599 } 1600 else 1601 { 1602 ascending = true; 1603 attributeName = token; 1604 } 1605 1606 final String matchingRuleID; 1607 final int colonPos = attributeName.indexOf(':'); 1608 if (colonPos >= 0) 1609 { 1610 matchingRuleID = attributeName.substring(colonPos+1); 1611 attributeName = attributeName.substring(0, colonPos); 1612 } 1613 else 1614 { 1615 matchingRuleID = null; 1616 } 1617 1618 final StringBuilder invalidReason = new StringBuilder(); 1619 if (! PersistUtils.isValidLDAPName(attributeName, false, invalidReason)) 1620 { 1621 throw new ArgumentException( 1622 ERR_LDAPSEARCH_SORT_ORDER_INVALID_VALUE.get( 1623 sortOrder.getIdentifierString())); 1624 } 1625 1626 sortKeyList.add( 1627 new SortKey(attributeName, matchingRuleID, (! ascending))); 1628 } 1629 1630 if (sortKeyList.isEmpty()) 1631 { 1632 throw new ArgumentException( 1633 ERR_LDAPSEARCH_SORT_ORDER_INVALID_VALUE.get( 1634 sortOrder.getIdentifierString())); 1635 } 1636 1637 final SortKey[] sortKeyArray = new SortKey[sortKeyList.size()]; 1638 sortKeyList.toArray(sortKeyArray); 1639 1640 sortRequestControl = new ServerSideSortRequestControl(sortKeyArray); 1641 } 1642 1643 1644 // If we should use the virtual list view request control, then validate the 1645 // argument value and pre-create the control. 1646 if (virtualListView.isPresent()) 1647 { 1648 try 1649 { 1650 final String[] elements = virtualListView.getValue().split(":"); 1651 if (elements.length == 4) 1652 { 1653 vlvRequestControl = new VirtualListViewRequestControl( 1654 Integer.parseInt(elements[2]), Integer.parseInt(elements[0]), 1655 Integer.parseInt(elements[1]), Integer.parseInt(elements[3]), 1656 null); 1657 } 1658 else if (elements.length == 3) 1659 { 1660 vlvRequestControl = new VirtualListViewRequestControl(elements[2], 1661 Integer.parseInt(elements[0]), Integer.parseInt(elements[1]), 1662 null); 1663 } 1664 else 1665 { 1666 throw new ArgumentException( 1667 ERR_LDAPSEARCH_VLV_INVALID_VALUE.get( 1668 virtualListView.getIdentifierString())); 1669 } 1670 } 1671 catch (final ArgumentException ae) 1672 { 1673 Debug.debugException(ae); 1674 throw ae; 1675 } 1676 catch (final Exception e) 1677 { 1678 Debug.debugException(e); 1679 throw new ArgumentException( 1680 ERR_LDAPSEARCH_VLV_INVALID_VALUE.get( 1681 virtualListView.getIdentifierString()), 1682 e); 1683 } 1684 } 1685 1686 1687 if (joinRule.isPresent()) 1688 { 1689 final JoinRule rule; 1690 try 1691 { 1692 final String[] elements = joinRule.getValue().toLowerCase().split(":"); 1693 final String ruleName = StaticUtils.toLowerCase(elements[0]); 1694 if (ruleName.equals("dn")) 1695 { 1696 rule = JoinRule.createDNJoin(elements[1]); 1697 } 1698 else if (ruleName.equals("reverse-dn") || ruleName.equals("reversedn")) 1699 { 1700 rule = JoinRule.createReverseDNJoin(elements[1]); 1701 } 1702 else if (ruleName.equals("equals") || ruleName.equals("equality")) 1703 { 1704 rule = JoinRule.createEqualityJoin(elements[1], elements[2], false); 1705 } 1706 else if (ruleName.equals("contains") || ruleName.equals("substring")) 1707 { 1708 rule = JoinRule.createContainsJoin(elements[1], elements[2], false); 1709 } 1710 else 1711 { 1712 throw new ArgumentException( 1713 ERR_LDAPSEARCH_JOIN_RULE_INVALID_VALUE.get( 1714 joinRule.getIdentifierString())); 1715 } 1716 } 1717 catch (final ArgumentException ae) 1718 { 1719 Debug.debugException(ae); 1720 throw ae; 1721 } 1722 catch (final Exception e) 1723 { 1724 Debug.debugException(e); 1725 throw new ArgumentException( 1726 ERR_LDAPSEARCH_JOIN_RULE_INVALID_VALUE.get( 1727 joinRule.getIdentifierString()), 1728 e); 1729 } 1730 1731 final JoinBaseDN joinBase; 1732 if (joinBaseDN.isPresent()) 1733 { 1734 final String s = StaticUtils.toLowerCase(joinBaseDN.getValue()); 1735 if (s.equals("search-base") || s.equals("search-base-dn")) 1736 { 1737 joinBase = JoinBaseDN.createUseSearchBaseDN(); 1738 } 1739 else if (s.equals("source-entry-dn") || s.equals("source-dn")) 1740 { 1741 joinBase = JoinBaseDN.createUseSourceEntryDN(); 1742 } 1743 else 1744 { 1745 try 1746 { 1747 final DN dn = new DN(joinBaseDN.getValue()); 1748 joinBase = JoinBaseDN.createUseCustomBaseDN(joinBaseDN.getValue()); 1749 } 1750 catch (final Exception e) 1751 { 1752 Debug.debugException(e); 1753 throw new ArgumentException( 1754 ERR_LDAPSEARCH_JOIN_BASE_DN_INVALID_VALUE.get( 1755 joinBaseDN.getIdentifierString()), 1756 e); 1757 } 1758 } 1759 } 1760 else 1761 { 1762 joinBase = JoinBaseDN.createUseSearchBaseDN(); 1763 } 1764 1765 final String[] joinAttrs; 1766 if (joinRequestedAttribute.isPresent()) 1767 { 1768 final List<String> valueList = joinRequestedAttribute.getValues(); 1769 joinAttrs = new String[valueList.size()]; 1770 valueList.toArray(joinAttrs); 1771 } 1772 else 1773 { 1774 joinAttrs = null; 1775 } 1776 1777 joinRequestControl = new JoinRequestControl(new JoinRequestValue(rule, 1778 joinBase, joinScope.getValue(), DereferencePolicy.NEVER, 1779 joinSizeLimit.getValue(), joinFilter.getValue(), joinAttrs, 1780 joinRequireMatch.isPresent(), null)); 1781 } 1782 1783 1784 // Parse the dereference policy. 1785 final String derefStr = 1786 StaticUtils.toLowerCase(dereferencePolicy.getValue()); 1787 if (derefStr.equals("always")) 1788 { 1789 derefPolicy = DereferencePolicy.ALWAYS; 1790 } 1791 else if (derefStr.equals("search")) 1792 { 1793 derefPolicy = DereferencePolicy.SEARCHING; 1794 } 1795 else if (derefStr.equals("find")) 1796 { 1797 derefPolicy = DereferencePolicy.FINDING; 1798 } 1799 else 1800 { 1801 derefPolicy = DereferencePolicy.NEVER; 1802 } 1803 1804 1805 // See if any entry transformations need to be applied. 1806 final ArrayList<EntryTransformation> transformations = 1807 new ArrayList<EntryTransformation>(5); 1808 if (excludeAttribute.isPresent()) 1809 { 1810 transformations.add(new ExcludeAttributeTransformation(null, 1811 excludeAttribute.getValues())); 1812 } 1813 1814 if (redactAttribute.isPresent()) 1815 { 1816 transformations.add(new RedactAttributeTransformation(null, true, 1817 (! hideRedactedValueCount.isPresent()), 1818 redactAttribute.getValues())); 1819 } 1820 1821 if (scrambleAttribute.isPresent()) 1822 { 1823 final Long randomSeed; 1824 if (scrambleRandomSeed.isPresent()) 1825 { 1826 randomSeed = scrambleRandomSeed.getValue().longValue(); 1827 } 1828 else 1829 { 1830 randomSeed = null; 1831 } 1832 1833 transformations.add(new ScrambleAttributeTransformation(null, randomSeed, 1834 true, scrambleAttribute.getValues(), scrambleJSONField.getValues())); 1835 } 1836 1837 if (renameAttributeFrom.isPresent()) 1838 { 1839 if (renameAttributeFrom.getNumOccurrences() != 1840 renameAttributeTo.getNumOccurrences()) 1841 { 1842 throw new ArgumentException( 1843 ERR_LDAPSEARCH_RENAME_ATTRIBUTE_MISMATCH.get()); 1844 } 1845 1846 final Iterator<String> sourceIterator = 1847 renameAttributeFrom.getValues().iterator(); 1848 final Iterator<String> targetIterator = 1849 renameAttributeTo.getValues().iterator(); 1850 while (sourceIterator.hasNext()) 1851 { 1852 transformations.add(new RenameAttributeTransformation(null, 1853 sourceIterator.next(), targetIterator.next(), true)); 1854 } 1855 } 1856 1857 if (moveSubtreeFrom.isPresent()) 1858 { 1859 if (moveSubtreeFrom.getNumOccurrences() != 1860 moveSubtreeTo.getNumOccurrences()) 1861 { 1862 throw new ArgumentException(ERR_LDAPSEARCH_MOVE_SUBTREE_MISMATCH.get()); 1863 } 1864 1865 final Iterator<DN> sourceIterator = 1866 moveSubtreeFrom.getValues().iterator(); 1867 final Iterator<DN> targetIterator = moveSubtreeTo.getValues().iterator(); 1868 while (sourceIterator.hasNext()) 1869 { 1870 transformations.add(new MoveSubtreeTransformation(sourceIterator.next(), 1871 targetIterator.next())); 1872 } 1873 } 1874 1875 if (! transformations.isEmpty()) 1876 { 1877 entryTransformations = transformations; 1878 } 1879 1880 1881 // Create the output handler. 1882 final String outputFormatStr = 1883 StaticUtils.toLowerCase(outputFormat.getValue()); 1884 if (outputFormatStr.equals("json")) 1885 { 1886 outputHandler = new JSONLDAPSearchOutputHandler(this); 1887 } 1888 else if (outputFormatStr.equals("csv") || 1889 outputFormatStr.equals("tab-delimited")) 1890 { 1891 // These output formats cannot be used with the --ldapURLFile argument. 1892 if (ldapURLFile.isPresent()) 1893 { 1894 throw new ArgumentException( 1895 ERR_LDAPSEARCH_OUTPUT_FORMAT_NOT_SUPPORTED_WITH_URLS.get( 1896 outputFormat.getValue(), ldapURLFile.getIdentifierString())); 1897 } 1898 1899 // These output formats require the requested attributes to be specified 1900 // via the --requestedAttribute argument rather than as unnamed trailing 1901 // arguments. 1902 final List<String> requestedAttributes = requestedAttribute.getValues(); 1903 if ((requestedAttributes == null) || requestedAttributes.isEmpty()) 1904 { 1905 throw new ArgumentException( 1906 ERR_LDAPSEARCH_OUTPUT_FORMAT_REQUIRES_REQUESTED_ATTR_ARG.get( 1907 outputFormat.getValue(), 1908 requestedAttribute.getIdentifierString())); 1909 } 1910 1911 switch (trailingArgs.size()) 1912 { 1913 case 0: 1914 // This is fine. 1915 break; 1916 1917 case 1: 1918 // Make sure that the trailing argument is a filter rather than a 1919 // requested attribute. It's sufficient to ensure that neither the 1920 // filter nor filterFile argument was provided. 1921 if (filter.isPresent() || filterFile.isPresent()) 1922 { 1923 throw new ArgumentException( 1924 ERR_LDAPSEARCH_OUTPUT_FORMAT_REQUIRES_REQUESTED_ATTR_ARG.get( 1925 outputFormat.getValue(), 1926 requestedAttribute.getIdentifierString())); 1927 } 1928 break; 1929 1930 default: 1931 throw new ArgumentException( 1932 ERR_LDAPSEARCH_OUTPUT_FORMAT_REQUIRES_REQUESTED_ATTR_ARG.get( 1933 outputFormat.getValue(), 1934 requestedAttribute.getIdentifierString())); 1935 } 1936 1937 outputHandler = new ColumnFormatterLDAPSearchOutputHandler(this, 1938 (outputFormatStr.equals("csv") 1939 ? OutputFormat.CSV 1940 : OutputFormat.TAB_DELIMITED_TEXT), 1941 requestedAttributes, WRAP_COLUMN); 1942 } 1943 else 1944 { 1945 outputHandler = new LDIFLDAPSearchOutputHandler(this, WRAP_COLUMN); 1946 } 1947 } 1948 1949 1950 1951 /** 1952 * {@inheritDoc} 1953 */ 1954 @Override() 1955 public LDAPConnectionOptions getConnectionOptions() 1956 { 1957 final LDAPConnectionOptions options = new LDAPConnectionOptions(); 1958 1959 options.setUseSynchronousMode(true); 1960 options.setFollowReferrals(followReferrals.isPresent()); 1961 options.setUnsolicitedNotificationHandler(this); 1962 1963 return options; 1964 } 1965 1966 1967 1968 /** 1969 * {@inheritDoc} 1970 */ 1971 @Override() 1972 public ResultCode doToolProcessing() 1973 { 1974 // If we should use an output file, then set that up now. Otherwise, write 1975 // the header to standard output. 1976 if (outputFile.isPresent()) 1977 { 1978 if (! separateOutputFilePerSearch.isPresent()) 1979 { 1980 try 1981 { 1982 final FileOutputStream fos = 1983 new FileOutputStream(outputFile.getValue()); 1984 if (teeResultsToStandardOut.isPresent()) 1985 { 1986 outStream = new PrintStream(new TeeOutputStream(fos, getOut())); 1987 } 1988 else 1989 { 1990 outStream = new PrintStream(fos); 1991 } 1992 errStream = outStream; 1993 } 1994 catch (final Exception e) 1995 { 1996 Debug.debugException(e); 1997 wrapErr(0, WRAP_COLUMN, ERR_LDAPSEARCH_CANNOT_OPEN_OUTPUT_FILE.get( 1998 outputFile.getValue().getAbsolutePath(), 1999 StaticUtils.getExceptionMessage(e))); 2000 return ResultCode.LOCAL_ERROR; 2001 } 2002 2003 outputHandler.formatHeader(); 2004 } 2005 } 2006 else 2007 { 2008 outputHandler.formatHeader(); 2009 } 2010 2011 2012 // Examine the arguments to determine the sets of controls to use for each 2013 // type of request. 2014 final List<Control> searchControls = getSearchControls(); 2015 2016 2017 // If appropriate, ensure that any search result entries that include 2018 // base64-encoded attribute values will also include comments that attempt 2019 // to provide a human-readable representation of that value. 2020 final boolean originalCommentAboutBase64EncodedValues = 2021 LDIFWriter.commentAboutBase64EncodedValues(); 2022 LDIFWriter.setCommentAboutBase64EncodedValues( 2023 ! suppressBase64EncodedValueComments.isPresent()); 2024 2025 2026 LDAPConnectionPool pool = null; 2027 try 2028 { 2029 // Create a connection pool that will be used to communicate with the 2030 // directory server. 2031 if (! dryRun.isPresent()) 2032 { 2033 try 2034 { 2035 final StartAdministrativeSessionPostConnectProcessor p; 2036 if (useAdministrativeSession.isPresent()) 2037 { 2038 p = new StartAdministrativeSessionPostConnectProcessor( 2039 new StartAdministrativeSessionExtendedRequest(getToolName(), 2040 true)); 2041 } 2042 else 2043 { 2044 p = null; 2045 } 2046 2047 pool = getConnectionPool(1, 1, 0, p, null, true, 2048 new ReportBindResultLDAPConnectionPoolHealthCheck(this, true, 2049 false)); 2050 } 2051 catch (final LDAPException le) 2052 { 2053 // This shouldn't happen since the pool won't throw an exception if an 2054 // attempt to create an initial connection fails. 2055 Debug.debugException(le); 2056 commentToErr(ERR_LDAPSEARCH_CANNOT_CREATE_CONNECTION_POOL.get( 2057 StaticUtils.getExceptionMessage(le))); 2058 return le.getResultCode(); 2059 } 2060 2061 if (retryFailedOperations.isPresent()) 2062 { 2063 pool.setRetryFailedOperationsDueToInvalidConnections(true); 2064 } 2065 } 2066 2067 2068 // If appropriate, create a rate limiter. 2069 final FixedRateBarrier rateLimiter; 2070 if (ratePerSecond.isPresent()) 2071 { 2072 rateLimiter = new FixedRateBarrier(1000L, ratePerSecond.getValue()); 2073 } 2074 else 2075 { 2076 rateLimiter = null; 2077 } 2078 2079 2080 // If one or more LDAP URL files are provided, then construct search 2081 // requests from those URLs. 2082 if (ldapURLFile.isPresent()) 2083 { 2084 return searchWithLDAPURLs(pool, rateLimiter, searchControls); 2085 } 2086 2087 2088 // Get the set of requested attributes, as a combination of the 2089 // requestedAttribute argument values and any trailing arguments. 2090 final ArrayList<String> attrList = new ArrayList<String>(10); 2091 if (requestedAttribute.isPresent()) 2092 { 2093 attrList.addAll(requestedAttribute.getValues()); 2094 } 2095 2096 final List<String> trailingArgs = parser.getTrailingArguments(); 2097 if (! trailingArgs.isEmpty()) 2098 { 2099 final Iterator<String> trailingArgIterator = trailingArgs.iterator(); 2100 if (! (filter.isPresent() || filterFile.isPresent())) 2101 { 2102 trailingArgIterator.next(); 2103 } 2104 2105 while (trailingArgIterator.hasNext()) 2106 { 2107 attrList.add(trailingArgIterator.next()); 2108 } 2109 } 2110 2111 final String[] attributes = new String[attrList.size()]; 2112 attrList.toArray(attributes); 2113 2114 2115 // If either or both the filter or filterFile arguments are provided, then 2116 // use them to get the filters to process. Otherwise, the first trailing 2117 // argument should be a filter. 2118 ResultCode resultCode = ResultCode.SUCCESS; 2119 if (filter.isPresent() || filterFile.isPresent()) 2120 { 2121 if (filter.isPresent()) 2122 { 2123 for (final Filter f : filter.getValues()) 2124 { 2125 final ResultCode rc = searchWithFilter(pool, f, attributes, 2126 rateLimiter, searchControls); 2127 if (rc != ResultCode.SUCCESS) 2128 { 2129 if (resultCode == ResultCode.SUCCESS) 2130 { 2131 resultCode = rc; 2132 } 2133 2134 if (! continueOnError.isPresent()) 2135 { 2136 return resultCode; 2137 } 2138 } 2139 } 2140 } 2141 2142 if (filterFile.isPresent()) 2143 { 2144 final ResultCode rc = searchWithFilterFile(pool, attributes, 2145 rateLimiter, searchControls); 2146 if (rc != ResultCode.SUCCESS) 2147 { 2148 if (resultCode == ResultCode.SUCCESS) 2149 { 2150 resultCode = rc; 2151 } 2152 2153 if (! continueOnError.isPresent()) 2154 { 2155 return resultCode; 2156 } 2157 } 2158 } 2159 } 2160 else 2161 { 2162 final Filter f; 2163 try 2164 { 2165 final String filterStr = 2166 parser.getTrailingArguments().iterator().next(); 2167 f = Filter.create(filterStr); 2168 } 2169 catch (final LDAPException le) 2170 { 2171 // This should never happen. 2172 Debug.debugException(le); 2173 displayResult(le.toLDAPResult()); 2174 return le.getResultCode(); 2175 } 2176 2177 resultCode = 2178 searchWithFilter(pool, f, attributes, rateLimiter, searchControls); 2179 } 2180 2181 return resultCode; 2182 } 2183 finally 2184 { 2185 if (pool != null) 2186 { 2187 try 2188 { 2189 pool.close(); 2190 } 2191 catch (final Exception e) 2192 { 2193 Debug.debugException(e); 2194 } 2195 } 2196 2197 if (outStream != null) 2198 { 2199 try 2200 { 2201 outStream.close(); 2202 outStream = null; 2203 } 2204 catch (final Exception e) 2205 { 2206 Debug.debugException(e); 2207 } 2208 } 2209 2210 if (errStream != null) 2211 { 2212 try 2213 { 2214 errStream.close(); 2215 errStream = null; 2216 } 2217 catch (final Exception e) 2218 { 2219 Debug.debugException(e); 2220 } 2221 } 2222 2223 LDIFWriter.setCommentAboutBase64EncodedValues( 2224 originalCommentAboutBase64EncodedValues); 2225 } 2226 } 2227 2228 2229 2230 /** 2231 * Processes a set of searches using LDAP URLs read from one or more files. 2232 * 2233 * @param pool The connection pool to use to communicate with the 2234 * directory server. 2235 * @param rateLimiter An optional fixed-rate barrier that can be used for 2236 * request rate limiting. 2237 * @param searchControls The set of controls to include in search requests. 2238 * 2239 * @return A result code indicating the result of the processing. 2240 */ 2241 private ResultCode searchWithLDAPURLs(final LDAPConnectionPool pool, 2242 final FixedRateBarrier rateLimiter, 2243 final List<Control> searchControls) 2244 { 2245 ResultCode resultCode = ResultCode.SUCCESS; 2246 for (final File f : ldapURLFile.getValues()) 2247 { 2248 BufferedReader reader = null; 2249 2250 try 2251 { 2252 reader = new BufferedReader(new FileReader(f)); 2253 while (true) 2254 { 2255 final String line = reader.readLine(); 2256 if (line == null) 2257 { 2258 break; 2259 } 2260 2261 if ((line.length() == 0) || line.startsWith("#")) 2262 { 2263 continue; 2264 } 2265 2266 final LDAPURL url; 2267 try 2268 { 2269 url = new LDAPURL(line); 2270 } 2271 catch (final LDAPException le) 2272 { 2273 Debug.debugException(le); 2274 2275 commentToErr(ERR_LDAPSEARCH_MALFORMED_LDAP_URL.get( 2276 f.getAbsolutePath(), line)); 2277 if (resultCode == ResultCode.SUCCESS) 2278 { 2279 resultCode = le.getResultCode(); 2280 } 2281 2282 if (continueOnError.isPresent()) 2283 { 2284 continue; 2285 } 2286 else 2287 { 2288 return resultCode; 2289 } 2290 } 2291 2292 final SearchRequest searchRequest = new SearchRequest( 2293 new LDAPSearchListener(outputHandler, entryTransformations), 2294 url.getBaseDN().toString(), scope.getValue(), derefPolicy, 2295 sizeLimit.getValue(), timeLimitSeconds.getValue(), 2296 typesOnly.isPresent(), url.getFilter(), url.getAttributes()); 2297 final ResultCode rc = 2298 doSearch(pool, searchRequest, rateLimiter, searchControls); 2299 if (rc != ResultCode.SUCCESS) 2300 { 2301 if (resultCode == ResultCode.SUCCESS) 2302 { 2303 resultCode = rc; 2304 } 2305 2306 if (! continueOnError.isPresent()) 2307 { 2308 return resultCode; 2309 } 2310 } 2311 } 2312 } 2313 catch (final IOException ioe) 2314 { 2315 commentToErr(ERR_LDAPSEARCH_CANNOT_READ_LDAP_URL_FILE.get( 2316 f.getAbsolutePath(), StaticUtils.getExceptionMessage(ioe))); 2317 return ResultCode.LOCAL_ERROR; 2318 } 2319 finally 2320 { 2321 if (reader != null) 2322 { 2323 try 2324 { 2325 reader.close(); 2326 } 2327 catch (final Exception e) 2328 { 2329 Debug.debugException(e); 2330 } 2331 } 2332 } 2333 } 2334 2335 return resultCode; 2336 } 2337 2338 2339 2340 /** 2341 * Processes a set of searches using filters read from one or more files. 2342 * 2343 * @param pool The connection pool to use to communicate with the 2344 * directory server. 2345 * @param attributes The set of attributes to request that the server 2346 * include in matching entries. 2347 * @param rateLimiter An optional fixed-rate barrier that can be used for 2348 * request rate limiting. 2349 * @param searchControls The set of controls to include in search requests. 2350 * 2351 * @return A result code indicating the result of the processing. 2352 */ 2353 private ResultCode searchWithFilterFile(final LDAPConnectionPool pool, 2354 final String[] attributes, 2355 final FixedRateBarrier rateLimiter, 2356 final List<Control> searchControls) 2357 { 2358 ResultCode resultCode = ResultCode.SUCCESS; 2359 for (final File f : filterFile.getValues()) 2360 { 2361 FilterFileReader reader = null; 2362 2363 try 2364 { 2365 reader = new FilterFileReader(f); 2366 while (true) 2367 { 2368 final Filter searchFilter; 2369 try 2370 { 2371 searchFilter = reader.readFilter(); 2372 } 2373 catch (final LDAPException le) 2374 { 2375 Debug.debugException(le); 2376 commentToErr(ERR_LDAPSEARCH_MALFORMED_FILTER.get( 2377 f.getAbsolutePath(), le.getMessage())); 2378 if (resultCode == ResultCode.SUCCESS) 2379 { 2380 resultCode = le.getResultCode(); 2381 } 2382 2383 if (continueOnError.isPresent()) 2384 { 2385 continue; 2386 } 2387 else 2388 { 2389 return resultCode; 2390 } 2391 } 2392 2393 if (searchFilter == null) 2394 { 2395 break; 2396 } 2397 2398 final ResultCode rc = searchWithFilter(pool, searchFilter, attributes, 2399 rateLimiter, searchControls); 2400 if (rc != ResultCode.SUCCESS) 2401 { 2402 if (resultCode == ResultCode.SUCCESS) 2403 { 2404 resultCode = rc; 2405 } 2406 2407 if (! continueOnError.isPresent()) 2408 { 2409 return resultCode; 2410 } 2411 } 2412 } 2413 } 2414 catch (final IOException ioe) 2415 { 2416 Debug.debugException(ioe); 2417 commentToErr(ERR_LDAPSEARCH_CANNOT_READ_FILTER_FILE.get( 2418 f.getAbsolutePath(), StaticUtils.getExceptionMessage(ioe))); 2419 return ResultCode.LOCAL_ERROR; 2420 } 2421 finally 2422 { 2423 if (reader != null) 2424 { 2425 try 2426 { 2427 reader.close(); 2428 } 2429 catch (final Exception e) 2430 { 2431 Debug.debugException(e); 2432 } 2433 } 2434 } 2435 } 2436 2437 return resultCode; 2438 } 2439 2440 2441 2442 /** 2443 * Processes a search using the provided filter. 2444 * 2445 * @param pool The connection pool to use to communicate with the 2446 * directory server. 2447 * @param filter The filter to use for the search. 2448 * @param attributes The set of attributes to request that the server 2449 * include in matching entries. 2450 * @param rateLimiter An optional fixed-rate barrier that can be used for 2451 * request rate limiting. 2452 * @param searchControls The set of controls to include in search requests. 2453 * 2454 * @return A result code indicating the result of the processing. 2455 */ 2456 private ResultCode searchWithFilter(final LDAPConnectionPool pool, 2457 final Filter filter, 2458 final String[] attributes, 2459 final FixedRateBarrier rateLimiter, 2460 final List<Control> searchControls) 2461 { 2462 final SearchRequest searchRequest = new SearchRequest( 2463 new LDAPSearchListener(outputHandler, entryTransformations), 2464 baseDN.getStringValue(), scope.getValue(), derefPolicy, 2465 sizeLimit.getValue(), timeLimitSeconds.getValue(), 2466 typesOnly.isPresent(), filter, attributes); 2467 return doSearch(pool, searchRequest, rateLimiter, searchControls); 2468 } 2469 2470 2471 2472 /** 2473 * Processes a search with the provided information. 2474 * 2475 * @param pool The connection pool to use to communicate with the 2476 * directory server. 2477 * @param searchRequest The search request to process. 2478 * @param rateLimiter An optional fixed-rate barrier that can be used for 2479 * request rate limiting. 2480 * @param searchControls The set of controls to include in search requests. 2481 * 2482 * @return A result code indicating the result of the processing. 2483 */ 2484 private ResultCode doSearch(final LDAPConnectionPool pool, 2485 final SearchRequest searchRequest, 2486 final FixedRateBarrier rateLimiter, 2487 final List<Control> searchControls) 2488 { 2489 if (separateOutputFilePerSearch.isPresent()) 2490 { 2491 try 2492 { 2493 final String path = outputFile.getValue().getAbsolutePath() + '.' + 2494 outputFileCounter.getAndIncrement(); 2495 final FileOutputStream fos = new FileOutputStream(path); 2496 if (teeResultsToStandardOut.isPresent()) 2497 { 2498 outStream = new PrintStream(new TeeOutputStream(fos, getOut())); 2499 } 2500 else 2501 { 2502 outStream = new PrintStream(fos); 2503 } 2504 errStream = outStream; 2505 } 2506 catch (final Exception e) 2507 { 2508 Debug.debugException(e); 2509 wrapErr(0, WRAP_COLUMN, ERR_LDAPSEARCH_CANNOT_OPEN_OUTPUT_FILE.get( 2510 outputFile.getValue().getAbsolutePath(), 2511 StaticUtils.getExceptionMessage(e))); 2512 return ResultCode.LOCAL_ERROR; 2513 } 2514 2515 outputHandler.formatHeader(); 2516 } 2517 2518 try 2519 { 2520 if (rateLimiter != null) 2521 { 2522 rateLimiter.await(); 2523 } 2524 2525 2526 ASN1OctetString pagedResultsCookie = null; 2527 boolean multiplePages = false; 2528 long totalEntries = 0; 2529 long totalReferences = 0; 2530 2531 SearchResult searchResult; 2532 try 2533 { 2534 while (true) 2535 { 2536 searchRequest.setControls(searchControls); 2537 if (simplePageSize.isPresent()) 2538 { 2539 searchRequest.addControl(new SimplePagedResultsControl( 2540 simplePageSize.getValue(), pagedResultsCookie)); 2541 } 2542 2543 if (dryRun.isPresent()) 2544 { 2545 searchResult = new SearchResult(-1, ResultCode.SUCCESS, 2546 INFO_LDAPSEARCH_DRY_RUN_REQUEST_NOT_SENT.get( 2547 dryRun.getIdentifierString(), 2548 String.valueOf(searchRequest)), 2549 null, null, 0, 0, null); 2550 break; 2551 } 2552 else 2553 { 2554 if (! terse.isPresent()) 2555 { 2556 if (verbose.isPresent() || persistentSearch.isPresent() || 2557 filterFile.isPresent() || ldapURLFile.isPresent() || 2558 (filter.isPresent() && (filter.getNumOccurrences() > 1))) 2559 { 2560 commentToOut(INFO_LDAPSEARCH_SENDING_SEARCH_REQUEST.get( 2561 String.valueOf(searchRequest))); 2562 } 2563 } 2564 searchResult = pool.search(searchRequest); 2565 } 2566 2567 if (searchResult.getEntryCount() > 0) 2568 { 2569 totalEntries += searchResult.getEntryCount(); 2570 } 2571 2572 if (searchResult.getReferenceCount() > 0) 2573 { 2574 totalReferences += searchResult.getReferenceCount(); 2575 } 2576 2577 if (simplePageSize.isPresent()) 2578 { 2579 final SimplePagedResultsControl pagedResultsControl; 2580 try 2581 { 2582 pagedResultsControl = SimplePagedResultsControl.get(searchResult); 2583 if (pagedResultsControl == null) 2584 { 2585 throw new LDAPSearchException(new SearchResult( 2586 searchResult.getMessageID(), ResultCode.CONTROL_NOT_FOUND, 2587 ERR_LDAPSEARCH_MISSING_PAGED_RESULTS_RESPONSE_CONTROL. 2588 get(), 2589 searchResult.getMatchedDN(), 2590 searchResult.getReferralURLs(), 2591 searchResult.getSearchEntries(), 2592 searchResult.getSearchReferences(), 2593 searchResult.getEntryCount(), 2594 searchResult.getReferenceCount(), 2595 searchResult.getResponseControls())); 2596 } 2597 2598 if (pagedResultsControl.moreResultsToReturn()) 2599 { 2600 if (verbose.isPresent()) 2601 { 2602 commentToOut( 2603 INFO_LDAPSEARCH_INTERMEDIATE_PAGED_SEARCH_RESULT.get()); 2604 displayResult(searchResult); 2605 } 2606 2607 multiplePages = true; 2608 pagedResultsCookie = pagedResultsControl.getCookie(); 2609 } 2610 else 2611 { 2612 break; 2613 } 2614 } 2615 catch (final LDAPException le) 2616 { 2617 Debug.debugException(le); 2618 throw new LDAPSearchException(new SearchResult( 2619 searchResult.getMessageID(), ResultCode.CONTROL_NOT_FOUND, 2620 ERR_LDAPSEARCH_CANNOT_DECODE_PAGED_RESULTS_RESPONSE_CONTROL. 2621 get(StaticUtils.getExceptionMessage(le)), 2622 searchResult.getMatchedDN(), searchResult.getReferralURLs(), 2623 searchResult.getSearchEntries(), 2624 searchResult.getSearchReferences(), 2625 searchResult.getEntryCount(), 2626 searchResult.getReferenceCount(), 2627 searchResult.getResponseControls())); 2628 } 2629 } 2630 else 2631 { 2632 break; 2633 } 2634 } 2635 } 2636 catch (final LDAPSearchException lse) 2637 { 2638 Debug.debugException(lse); 2639 searchResult = lse.toLDAPResult(); 2640 2641 if (searchResult.getEntryCount() > 0) 2642 { 2643 totalEntries += searchResult.getEntryCount(); 2644 } 2645 2646 if (searchResult.getReferenceCount() > 0) 2647 { 2648 totalReferences += searchResult.getReferenceCount(); 2649 } 2650 } 2651 2652 if ((searchResult.getResultCode() != ResultCode.SUCCESS) || 2653 (searchResult.getDiagnosticMessage() != null) || 2654 (! terse.isPresent())) 2655 { 2656 displayResult(searchResult); 2657 } 2658 2659 if (multiplePages && (! terse.isPresent())) 2660 { 2661 commentToOut(INFO_LDAPSEARCH_TOTAL_SEARCH_ENTRIES.get(totalEntries)); 2662 2663 if (totalReferences > 0) 2664 { 2665 commentToOut(INFO_LDAPSEARCH_TOTAL_SEARCH_REFERENCES.get( 2666 totalReferences)); 2667 } 2668 } 2669 2670 if (countEntries.isPresent()) 2671 { 2672 return ResultCode.valueOf((int) Math.min(totalEntries, 255)); 2673 } 2674 else 2675 { 2676 return searchResult.getResultCode(); 2677 } 2678 } 2679 finally 2680 { 2681 if (separateOutputFilePerSearch.isPresent()) 2682 { 2683 try 2684 { 2685 outStream.close(); 2686 } 2687 catch (final Exception e) 2688 { 2689 Debug.debugException(e); 2690 } 2691 2692 outStream = null; 2693 errStream = null; 2694 } 2695 } 2696 } 2697 2698 2699 2700 /** 2701 * Retrieves a list of the controls that should be used when processing search 2702 * operations. 2703 * 2704 * @return A list of the controls that should be used when processing search 2705 * operations. 2706 */ 2707 private List<Control> getSearchControls() 2708 { 2709 final ArrayList<Control> controls = new ArrayList<Control>(10); 2710 2711 if (searchControl.isPresent()) 2712 { 2713 controls.addAll(searchControl.getValues()); 2714 } 2715 2716 if (joinRequestControl != null) 2717 { 2718 controls.add(joinRequestControl); 2719 } 2720 2721 if (matchedValuesRequestControl != null) 2722 { 2723 controls.add(matchedValuesRequestControl); 2724 } 2725 2726 if (matchingEntryCountRequestControl != null) 2727 { 2728 controls.add(matchingEntryCountRequestControl); 2729 } 2730 2731 if (persistentSearchRequestControl != null) 2732 { 2733 controls.add(persistentSearchRequestControl); 2734 } 2735 2736 if (sortRequestControl != null) 2737 { 2738 controls.add(sortRequestControl); 2739 } 2740 2741 if (vlvRequestControl != null) 2742 { 2743 controls.add(vlvRequestControl); 2744 } 2745 2746 if (accountUsable.isPresent()) 2747 { 2748 controls.add(new AccountUsableRequestControl(true)); 2749 } 2750 2751 if (includeReplicationConflictEntries.isPresent()) 2752 { 2753 controls.add(new ReturnConflictEntriesRequestControl(true)); 2754 } 2755 2756 if (includeSoftDeletedEntries.isPresent()) 2757 { 2758 final String valueStr = 2759 StaticUtils.toLowerCase(includeSoftDeletedEntries.getValue()); 2760 if (valueStr.equals("with-non-deleted-entries")) 2761 { 2762 controls.add(new SoftDeletedEntryAccessRequestControl(true, true, 2763 false)); 2764 } 2765 else if (valueStr.equals("without-non-deleted-entries")) 2766 { 2767 controls.add(new SoftDeletedEntryAccessRequestControl(true, false, 2768 false)); 2769 } 2770 else 2771 { 2772 controls.add(new SoftDeletedEntryAccessRequestControl(true, false, 2773 true)); 2774 } 2775 } 2776 2777 if (includeSubentries.isPresent()) 2778 { 2779 controls.add(new SubentriesRequestControl(true)); 2780 } 2781 2782 if (manageDsaIT.isPresent()) 2783 { 2784 controls.add(new ManageDsaITRequestControl(true)); 2785 } 2786 2787 if (realAttributesOnly.isPresent()) 2788 { 2789 controls.add(new RealAttributesOnlyRequestControl(true)); 2790 } 2791 2792 if (virtualAttributesOnly.isPresent()) 2793 { 2794 controls.add(new VirtualAttributesOnlyRequestControl(true)); 2795 } 2796 2797 if (excludeBranch.isPresent()) 2798 { 2799 final ArrayList<String> dns = 2800 new ArrayList<String>(excludeBranch.getValues().size()); 2801 for (final DN dn : excludeBranch.getValues()) 2802 { 2803 dns.add(dn.toString()); 2804 } 2805 controls.add(new ExcludeBranchRequestControl(true, dns)); 2806 } 2807 2808 if (assertionFilter.isPresent()) 2809 { 2810 controls.add(new AssertionRequestControl( 2811 assertionFilter.getValue(), true)); 2812 } 2813 2814 if (getEffectiveRightsAuthzID.isPresent()) 2815 { 2816 final String[] attributes; 2817 if (getEffectiveRightsAttribute.isPresent()) 2818 { 2819 attributes = new String[getEffectiveRightsAttribute.getValues().size()]; 2820 for (int i=0; i < attributes.length; i++) 2821 { 2822 attributes[i] = getEffectiveRightsAttribute.getValues().get(i); 2823 } 2824 } 2825 else 2826 { 2827 attributes = StaticUtils.NO_STRINGS; 2828 } 2829 2830 controls.add(new GetEffectiveRightsRequestControl(true, 2831 getEffectiveRightsAuthzID.getValue(), attributes)); 2832 } 2833 2834 if (operationPurpose.isPresent()) 2835 { 2836 controls.add(new OperationPurposeRequestControl(true, "ldapsearch", 2837 Version.NUMERIC_VERSION_STRING, "LDAPSearch.getSearchControls", 2838 operationPurpose.getValue())); 2839 } 2840 2841 if (proxyAs.isPresent()) 2842 { 2843 controls.add(new ProxiedAuthorizationV2RequestControl( 2844 proxyAs.getValue())); 2845 } 2846 2847 if (proxyV1As.isPresent()) 2848 { 2849 controls.add(new ProxiedAuthorizationV1RequestControl( 2850 proxyV1As.getValue())); 2851 } 2852 2853 if (suppressOperationalAttributeUpdates.isPresent()) 2854 { 2855 final EnumSet<SuppressType> suppressTypes = 2856 EnumSet.noneOf(SuppressType.class); 2857 for (final String s : suppressOperationalAttributeUpdates.getValues()) 2858 { 2859 if (s.equalsIgnoreCase("last-access-time")) 2860 { 2861 suppressTypes.add(SuppressType.LAST_ACCESS_TIME); 2862 } 2863 else if (s.equalsIgnoreCase("last-login-time")) 2864 { 2865 suppressTypes.add(SuppressType.LAST_LOGIN_TIME); 2866 } 2867 else if (s.equalsIgnoreCase("last-login-ip")) 2868 { 2869 suppressTypes.add(SuppressType.LAST_LOGIN_IP); 2870 } 2871 } 2872 2873 controls.add(new SuppressOperationalAttributeUpdateRequestControl( 2874 suppressTypes)); 2875 } 2876 2877 return controls; 2878 } 2879 2880 2881 2882 /** 2883 * Displays information about the provided result, including special 2884 * processing for a number of supported response controls. 2885 * 2886 * @param result The result to examine. 2887 */ 2888 void displayResult(final LDAPResult result) 2889 { 2890 outputHandler.formatResult(result); 2891 } 2892 2893 2894 2895 /** 2896 * Writes the provided message to the output stream. 2897 * 2898 * @param message The message to be written. 2899 */ 2900 void writeOut(final String message) 2901 { 2902 if (outStream == null) 2903 { 2904 out(message); 2905 } 2906 else 2907 { 2908 outStream.println(message); 2909 } 2910 } 2911 2912 2913 2914 /** 2915 * Writes the provided message to the error stream. 2916 * 2917 * @param message The message to be written. 2918 */ 2919 void writeErr(final String message) 2920 { 2921 if (errStream == null) 2922 { 2923 err(message); 2924 } 2925 else 2926 { 2927 errStream.println(message); 2928 } 2929 } 2930 2931 2932 2933 /** 2934 * Writes a line-wrapped, commented version of the provided message to 2935 * standard output. 2936 * 2937 * @param message The message to be written. 2938 */ 2939 private void commentToOut(final String message) 2940 { 2941 if (terse.isPresent()) 2942 { 2943 return; 2944 } 2945 2946 for (final String line : StaticUtils.wrapLine(message, (WRAP_COLUMN - 2))) 2947 { 2948 writeOut("# " + line); 2949 } 2950 } 2951 2952 2953 2954 /** 2955 * Writes a line-wrapped, commented version of the provided message to 2956 * standard error. 2957 * 2958 * @param message The message to be written. 2959 */ 2960 private void commentToErr(final String message) 2961 { 2962 for (final String line : StaticUtils.wrapLine(message, (WRAP_COLUMN - 2))) 2963 { 2964 writeErr("# " + line); 2965 } 2966 } 2967 2968 2969 2970 /** 2971 * Sets the output handler that should be used by this tool This is primarily 2972 * intended for testing purposes. 2973 * 2974 * @param outputHandler The output handler that should be used by this tool. 2975 */ 2976 void setOutputHandler(final LDAPSearchOutputHandler outputHandler) 2977 { 2978 this.outputHandler = outputHandler; 2979 } 2980 2981 2982 2983 /** 2984 * {@inheritDoc} 2985 */ 2986 @Override() 2987 public void handleUnsolicitedNotification(final LDAPConnection connection, 2988 final ExtendedResult notification) 2989 { 2990 outputHandler.formatUnsolicitedNotification(connection, notification); 2991 } 2992 2993 2994 2995 /** 2996 * {@inheritDoc} 2997 */ 2998 @Override() 2999 public LinkedHashMap<String[],String> getExampleUsages() 3000 { 3001 final LinkedHashMap<String[],String> examples = 3002 new LinkedHashMap<String[],String>(5); 3003 3004 String[] args = 3005 { 3006 "--hostname", "directory.example.com", 3007 "--port", "389", 3008 "--bindDN", "uid=jdoe,ou=People,dc=example,dc=com", 3009 "--bindPassword", "password", 3010 "--baseDN", "ou=People,dc=example,dc=com", 3011 "--searchScope", "sub", 3012 "(uid=jqpublic)", 3013 "givenName", 3014 "sn", 3015 "mail" 3016 }; 3017 examples.put(args, INFO_LDAPSEARCH_EXAMPLE_1.get()); 3018 3019 3020 args = new String[] 3021 { 3022 "--hostname", "directory.example.com", 3023 "--port", "636", 3024 "--useSSL", 3025 "--saslOption", "mech=PLAIN", 3026 "--saslOption", "authID=u:jdoe", 3027 "--bindPasswordFile", "/path/to/password/file", 3028 "--baseDN", "ou=People,dc=example,dc=com", 3029 "--searchScope", "sub", 3030 "--filterFile", "/path/to/filter/file", 3031 "--outputFile", "/path/to/base/output/file", 3032 "--separateOutputFilePerSearch", 3033 "--requestedAttribute", "*", 3034 "--requestedAttribute", "+" 3035 }; 3036 examples.put(args, INFO_LDAPSEARCH_EXAMPLE_2.get()); 3037 3038 3039 args = new String[] 3040 { 3041 "--hostname", "directory.example.com", 3042 "--port", "389", 3043 "--useStartTLS", 3044 "--trustStorePath", "/path/to/truststore/file", 3045 "--baseDN", "", 3046 "--searchScope", "base", 3047 "--outputFile", "/path/to/output/file", 3048 "--teeResultsToStandardOut", 3049 "(objectClass=*)", 3050 "*", 3051 "+" 3052 }; 3053 examples.put(args, INFO_LDAPSEARCH_EXAMPLE_3.get()); 3054 3055 3056 args = new String[] 3057 { 3058 "--hostname", "directory.example.com", 3059 "--port", "389", 3060 "--bindDN", "uid=admin,dc=example,dc=com", 3061 "--baseDN", "dc=example,dc=com", 3062 "--searchScope", "sub", 3063 "--outputFile", "/path/to/output/file", 3064 "--simplePageSize", "100", 3065 "(objectClass=*)", 3066 "*", 3067 "+" 3068 }; 3069 examples.put(args, INFO_LDAPSEARCH_EXAMPLE_4.get()); 3070 3071 3072 args = new String[] 3073 { 3074 "--hostname", "directory.example.com", 3075 "--port", "389", 3076 "--bindDN", "uid=admin,dc=example,dc=com", 3077 "--baseDN", "dc=example,dc=com", 3078 "--searchScope", "sub", 3079 "(&(givenName=John)(sn=Doe))", 3080 "debugsearchindex" 3081 }; 3082 examples.put(args, INFO_LDAPSEARCH_EXAMPLE_5.get()); 3083 3084 return examples; 3085 } 3086}