001/* 002 * Copyright 2020 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2020 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2020 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.ldif; 037 038 039 040import java.io.BufferedReader; 041import java.io.File; 042import java.io.FileInputStream; 043import java.io.FileOutputStream; 044import java.io.IOException; 045import java.io.InputStream; 046import java.io.InputStreamReader; 047import java.io.OutputStream; 048import java.util.ArrayList; 049import java.util.Arrays; 050import java.util.Collections; 051import java.util.HashSet; 052import java.util.Iterator; 053import java.util.LinkedHashMap; 054import java.util.LinkedHashSet; 055import java.util.List; 056import java.util.Map; 057import java.util.Set; 058import java.util.TreeMap; 059import java.util.concurrent.atomic.AtomicReference; 060import java.util.zip.GZIPOutputStream; 061 062import com.unboundid.ldap.listener.SearchEntryParer; 063import com.unboundid.ldap.sdk.DN; 064import com.unboundid.ldap.sdk.Entry; 065import com.unboundid.ldap.sdk.Filter; 066import com.unboundid.ldap.sdk.InternalSDKHelper; 067import com.unboundid.ldap.sdk.LDAPException; 068import com.unboundid.ldap.sdk.LDAPURL; 069import com.unboundid.ldap.sdk.ResultCode; 070import com.unboundid.ldap.sdk.SearchScope; 071import com.unboundid.ldap.sdk.Version; 072import com.unboundid.ldap.sdk.schema.EntryValidator; 073import com.unboundid.ldap.sdk.schema.Schema; 074import com.unboundid.ldap.sdk.unboundidds.tools.ToolUtils; 075import com.unboundid.util.CommandLineTool; 076import com.unboundid.util.Debug; 077import com.unboundid.util.NotNull; 078import com.unboundid.util.Nullable; 079import com.unboundid.util.ObjectPair; 080import com.unboundid.util.PassphraseEncryptedOutputStream; 081import com.unboundid.util.StaticUtils; 082import com.unboundid.util.ThreadSafety; 083import com.unboundid.util.ThreadSafetyLevel; 084import com.unboundid.util.args.ArgumentException; 085import com.unboundid.util.args.ArgumentParser; 086import com.unboundid.util.args.BooleanArgument; 087import com.unboundid.util.args.DNArgument; 088import com.unboundid.util.args.FileArgument; 089import com.unboundid.util.args.IntegerArgument; 090import com.unboundid.util.args.ScopeArgument; 091 092import static com.unboundid.ldif.LDIFMessages.*; 093 094 095 096/** 097 * This class provides a command-line tool that can be used to search for 098 * entries matching a given set of criteria in an LDIF file. 099 */ 100@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE) 101public final class LDIFSearch 102 extends CommandLineTool 103{ 104 /** 105 * The server root directory for the Ping Identity Directory Server (or 106 * related Ping Identity server product) that contains this tool, if 107 * applicable. 108 */ 109 @Nullable private static final File PING_SERVER_ROOT = 110 InternalSDKHelper.getPingIdentityServerRoot(); 111 112 113 114 /** 115 * Indicates whether the tool is running as part of a Ping Identity Directory 116 * Server (or related Ping Identity Server Product) installation. 117 */ 118 private static final boolean PING_SERVER_AVAILABLE = 119 (PING_SERVER_ROOT != null); 120 121 122 123 /** 124 * The column at which to wrap long lines. 125 */ 126 private static final int WRAP_COLUMN = StaticUtils.TERMINAL_WIDTH_COLUMNS - 1; 127 128 129 130 // The argument parser for this tool. 131 @Nullable private volatile ArgumentParser parser; 132 133 // The completion message for this tool. 134 @NotNull private final AtomicReference<String> completionMessage; 135 136 // Indicates whether the LDIF encryption passphrase file has been read. 137 private volatile boolean ldifEncryptionPassphraseFileRead; 138 139 // Encryption passphrases used thus far. 140 @NotNull private final List<char[]> inputEncryptionPassphrases; 141 142 // The list of LDAP URLs to use when processing searches, mapped to the 143 // corresponding search entry parers. 144 @NotNull private final List<LDAPURL> searchURLs; 145 146 // The command-line arguments supported by this tool. 147 @Nullable private BooleanArgument checkSchema; 148 @Nullable private BooleanArgument compressOutput; 149 @Nullable private BooleanArgument doNotWrap; 150 @Nullable private BooleanArgument encryptOutput; 151 @Nullable private BooleanArgument isCompressed; 152 @Nullable private BooleanArgument overwriteExistingOutputFile; 153 @Nullable private BooleanArgument separateOutputFilePerSearch; 154 @Nullable private BooleanArgument stripTrailingSpaces; 155 @Nullable private DNArgument baseDN; 156 @Nullable private FileArgument filterFile; 157 @Nullable private FileArgument ldapURLFile; 158 @Nullable private FileArgument ldifEncryptionPassphraseFile; 159 @Nullable private FileArgument ldifFile; 160 @Nullable private FileArgument outputFile; 161 @Nullable private FileArgument outputEncryptionPassphraseFile; 162 @Nullable private FileArgument schemaPath; 163 @Nullable private IntegerArgument sizeLimit; 164 @Nullable private IntegerArgument timeLimitSeconds; 165 @Nullable private IntegerArgument wrapColumn; 166 @Nullable private ScopeArgument scope; 167 168 169 170 /** 171 * Invokes this tool with the provided set of command-line arguments. 172 * 173 * @param args The set of arguments provided to this tool. It may be 174 * empty but must not be {@code null}. 175 */ 176 public static void main(@NotNull final String... args) 177 { 178 final ResultCode resultCode = main(System.out, System.err, args); 179 if (resultCode != ResultCode.SUCCESS) 180 { 181 System.exit(resultCode.intValue()); 182 } 183 } 184 185 186 187 /** 188 * Invokes this tool with the provided set of command-line arguments, using 189 * the given output and error streams. 190 * 191 * @param out The output stream to use for standard output. It may be 192 * {@code null} if standard output should be suppressed. 193 * @param err The output stream to use for standard error. It may be 194 * {@code null} if standard error should be suppressed. 195 * @param args The set of arguments provided to this tool. It may be 196 * empty but must not be {@code null}. 197 * 198 * @return A result code indicating the status of processing. Any result 199 * code other than {@link ResultCode#SUCCESS} should be considered 200 * an error. 201 */ 202 @NotNull() 203 public static ResultCode main(@Nullable final OutputStream out, 204 @Nullable final OutputStream err, 205 @NotNull final String... args) 206 { 207 final LDIFSearch tool = new LDIFSearch(out, err); 208 return tool.runTool(args); 209 } 210 211 212 213 /** 214 * Creates a new instance of this tool with the provided output and error 215 * streams. 216 * 217 * @param out The output stream to use for standard output. It may be 218 * {@code null} if standard output should be suppressed. 219 * @param err The output stream to use for standard error. It may be 220 * {@code null} if standard error should be suppressed. 221 */ 222 public LDIFSearch(@Nullable final OutputStream out, 223 @Nullable final OutputStream err) 224 { 225 super(out, err); 226 227 parser = null; 228 completionMessage = new AtomicReference<>(); 229 inputEncryptionPassphrases = new ArrayList<>(5); 230 searchURLs = new ArrayList<>(); 231 ldifEncryptionPassphraseFileRead = false; 232 233 checkSchema = null; 234 compressOutput = null; 235 doNotWrap = null; 236 encryptOutput = null; 237 isCompressed = null; 238 overwriteExistingOutputFile = null; 239 separateOutputFilePerSearch = null; 240 stripTrailingSpaces = null; 241 baseDN = null; 242 filterFile = null; 243 ldapURLFile = null; 244 ldifEncryptionPassphraseFile = null; 245 ldifFile = null; 246 outputFile = null; 247 outputEncryptionPassphraseFile = null; 248 schemaPath = null; 249 sizeLimit = null; 250 timeLimitSeconds = null; 251 wrapColumn = null; 252 scope = null; 253 } 254 255 256 257 /** 258 * {@inheritDoc} 259 */ 260 @Override() 261 @NotNull() 262 public String getToolName() 263 { 264 return "ldifsearch"; 265 } 266 267 268 269 /** 270 * {@inheritDoc} 271 */ 272 @Override() 273 @NotNull() 274 public String getToolDescription() 275 { 276 return INFO_LDIFSEARCH_TOOL_DESCRIPTION.get(); 277 } 278 279 280 281 /** 282 * {@inheritDoc} 283 */ 284 @Override() 285 @NotNull() 286 public String getToolVersion() 287 { 288 return Version.NUMERIC_VERSION_STRING; 289 } 290 291 292 293 /** 294 * {@inheritDoc} 295 */ 296 @Override() 297 public int getMinTrailingArguments() 298 { 299 return 0; 300 } 301 302 303 304 /** 305 * {@inheritDoc} 306 */ 307 @Override() 308 public int getMaxTrailingArguments() 309 { 310 return -1; 311 } 312 313 314 315 /** 316 * {@inheritDoc} 317 */ 318 @Override() 319 @NotNull() 320 public String getTrailingArgumentsPlaceholder() 321 { 322 return INFO_LDIFSEARCH_TRAILING_ARGS_PLACEHOLDER.get(); 323 } 324 325 326 327 /** 328 * {@inheritDoc} 329 */ 330 @Override() 331 public boolean supportsInteractiveMode() 332 { 333 return true; 334 } 335 336 337 338 /** 339 * {@inheritDoc} 340 */ 341 @Override() 342 public boolean defaultsToInteractiveMode() 343 { 344 return true; 345 } 346 347 348 349 /** 350 * {@inheritDoc} 351 */ 352 @Override() 353 public boolean supportsPropertiesFile() 354 { 355 return true; 356 } 357 358 359 360 /** 361 * {@inheritDoc} 362 */ 363 @Override() 364 @Nullable() 365 protected String getToolCompletionMessage() 366 { 367 return completionMessage.get(); 368 } 369 370 371 372 /** 373 * {@inheritDoc} 374 */ 375 @Override() 376 public void addToolArguments(@NotNull final ArgumentParser parser) 377 throws ArgumentException 378 { 379 this.parser = parser; 380 381 382 ldifFile = new FileArgument('l', "ldifFile", true, 0, null, 383 INFO_LDIFSEARCH_ARG_DESC_LDIF_FILE.get(), true, true, true, false); 384 ldifFile.addLongIdentifier("ldif-file", true); 385 ldifFile.addLongIdentifier("inputFile", true); 386 ldifFile.addLongIdentifier("input-file", true); 387 ldifFile.setArgumentGroupName(INFO_LDIFSEARCH_ARG_GROUP_INPUT.get()); 388 parser.addArgument(ldifFile); 389 390 391 final String ldifPWDesc; 392 if (PING_SERVER_AVAILABLE) 393 { 394 ldifPWDesc = INFO_LDIFSEARCH_ARG_DESC_LDIF_PW_FILE_PING_SERVER.get(); 395 } 396 else 397 { 398 ldifPWDesc = INFO_LDIFSEARCH_ARG_DESC_LDIF_PW_FILE_STANDALONE.get(); 399 } 400 ldifEncryptionPassphraseFile = new FileArgument(null, 401 "ldifEncryptionPassphraseFile", false, 1, null, ldifPWDesc, true, 402 true, true, false); 403 ldifEncryptionPassphraseFile.addLongIdentifier( 404 "ldif-encryption-passphrase-file", true); 405 ldifEncryptionPassphraseFile.addLongIdentifier("ldifPassphraseFile", true); 406 ldifEncryptionPassphraseFile.addLongIdentifier("ldif-passphrase-file", 407 true); 408 ldifEncryptionPassphraseFile.addLongIdentifier("ldifEncryptionPasswordFile", 409 true); 410 ldifEncryptionPassphraseFile.addLongIdentifier( 411 "ldif-encryption-password-file", true); 412 ldifEncryptionPassphraseFile.addLongIdentifier("ldifPasswordFile", true); 413 ldifEncryptionPassphraseFile.addLongIdentifier("ldif-password-file", true); 414 ldifEncryptionPassphraseFile.addLongIdentifier( 415 "inputEncryptionPassphraseFile", true); 416 ldifEncryptionPassphraseFile.addLongIdentifier( 417 "input-encryption-passphrase-file", true); 418 ldifEncryptionPassphraseFile.addLongIdentifier("inputPassphraseFile", true); 419 ldifEncryptionPassphraseFile.addLongIdentifier("input-passphrase-file", 420 true); 421 ldifEncryptionPassphraseFile.addLongIdentifier( 422 "inputEncryptionPasswordFile", true); 423 ldifEncryptionPassphraseFile.addLongIdentifier( 424 "input-encryption-password-file", true); 425 ldifEncryptionPassphraseFile.addLongIdentifier("inputPasswordFile", true); 426 ldifEncryptionPassphraseFile.addLongIdentifier("input-password-file", true); 427 ldifEncryptionPassphraseFile.setArgumentGroupName( 428 INFO_LDIFSEARCH_ARG_GROUP_INPUT.get()); 429 parser.addArgument(ldifEncryptionPassphraseFile); 430 431 432 stripTrailingSpaces = new BooleanArgument(null, "stripTrailingSpaces", 1, 433 INFO_LDIFSEARCH_ARG_DESC_STRIP_TRAILING_SPACES.get()); 434 stripTrailingSpaces.addLongIdentifier("strip-trailing-spaces", true); 435 stripTrailingSpaces.addLongIdentifier("ignoreTrailingSpaces", true); 436 stripTrailingSpaces.addLongIdentifier("ignore-trailing-spaces", true); 437 stripTrailingSpaces.setArgumentGroupName( 438 INFO_LDIFSEARCH_ARG_GROUP_INPUT.get()); 439 parser.addArgument(stripTrailingSpaces); 440 441 442 final String schemaPathDesc; 443 if (PING_SERVER_AVAILABLE) 444 { 445 schemaPathDesc = INFO_LDIFSEARCH_ARG_DESC_SCHEMA_PATH_PING_SERVER.get(); 446 } 447 else 448 { 449 schemaPathDesc = INFO_LDIFSEARCH_ARG_DESC_SCHEMA_PATH_STANDALONE.get(); 450 } 451 schemaPath = new FileArgument(null, "schemaPath", false, 0, null, 452 schemaPathDesc, true, true, false, false); 453 schemaPath.addLongIdentifier("schema-path", true); 454 schemaPath.addLongIdentifier("schemaFile", true); 455 schemaPath.addLongIdentifier("schema-file", true); 456 schemaPath.addLongIdentifier("schemaDirectory", true); 457 schemaPath.addLongIdentifier("schema-directory", true); 458 schemaPath.addLongIdentifier("schema", true); 459 schemaPath.setArgumentGroupName(INFO_LDIFSEARCH_ARG_GROUP_INPUT.get()); 460 parser.addArgument(schemaPath); 461 462 463 checkSchema = new BooleanArgument(null, "checkSchema", 1, 464 INFO_LDIFSEARCH_ARG_DESC_CHECK_SCHEMA.get()); 465 checkSchema.addLongIdentifier("check-schema", true); 466 checkSchema.setArgumentGroupName(INFO_LDIFSEARCH_ARG_GROUP_INPUT.get()); 467 parser.addArgument(checkSchema); 468 469 470 isCompressed = new BooleanArgument(null, "isCompressed", 1, 471 INFO_LDIFSEARCH_ARG_DESC_IS_COMPRESSED.get()); 472 isCompressed.addLongIdentifier("is-compressed", true); 473 isCompressed.setArgumentGroupName(INFO_LDIFSEARCH_ARG_GROUP_INPUT.get()); 474 isCompressed.setHidden(true); 475 parser.addArgument(isCompressed); 476 477 478 outputFile = new FileArgument('o', "outputFile", false, 1, null, 479 INFO_LDIFSEARCH_ARG_DESC_OUTPUT_FILE.get(), false, true, true, false); 480 outputFile.addLongIdentifier("output-file", true); 481 outputFile.addLongIdentifier("outputLDIF", true); 482 outputFile.addLongIdentifier("output-ldif", true); 483 outputFile.setArgumentGroupName(INFO_LDIFSEARCH_ARG_GROUP_OUTPUT.get()); 484 parser.addArgument(outputFile); 485 486 487 separateOutputFilePerSearch = new BooleanArgument(null, 488 "separateOutputFilePerSearch", 1, 489 INFO_LDIFSEARCH_ARG_DESC_SEPARATE_OUTPUT_FILES.get()); 490 separateOutputFilePerSearch.addLongIdentifier( 491 "separate-output-file-per-search", true); 492 separateOutputFilePerSearch.addLongIdentifier("separateOutputFiles", true); 493 separateOutputFilePerSearch.addLongIdentifier("separate-output-files", 494 true); 495 separateOutputFilePerSearch.setArgumentGroupName( 496 INFO_LDIFSEARCH_ARG_GROUP_OUTPUT.get()); 497 parser.addArgument(separateOutputFilePerSearch); 498 499 500 compressOutput = new BooleanArgument(null, "compressOutput", 1, 501 INFO_LDIFSEARCH_ARG_DESC_COMPRESS_OUTPUT.get()); 502 compressOutput.addLongIdentifier("compress-output", true); 503 compressOutput.addLongIdentifier("compressLDIF", true); 504 compressOutput.addLongIdentifier("compress-ldif", true); 505 compressOutput.addLongIdentifier("compress", true); 506 compressOutput.setArgumentGroupName(INFO_LDIFSEARCH_ARG_GROUP_OUTPUT.get()); 507 parser.addArgument(compressOutput); 508 509 510 encryptOutput = new BooleanArgument(null, "encryptOutput", 1, 511 INFO_LDIFSEARCH_ARG_DESC_ENCRYPT_OUTPUT.get()); 512 encryptOutput.addLongIdentifier("encrypt-output", true); 513 encryptOutput.addLongIdentifier("encryptLDIF", true); 514 encryptOutput.addLongIdentifier("encrypt-ldif", true); 515 encryptOutput.addLongIdentifier("encrypt", true); 516 encryptOutput.setArgumentGroupName(INFO_LDIFSEARCH_ARG_GROUP_OUTPUT.get()); 517 parser.addArgument(encryptOutput); 518 519 520 outputEncryptionPassphraseFile = new FileArgument(null, 521 "outputEncryptionPassphraseFile", false, 1, null, 522 INFO_LDIFSEARCH_ARG_DESC_OUTPUT_PW_FILE.get(), true, true, true, 523 false); 524 outputEncryptionPassphraseFile.addLongIdentifier( 525 "output-encryption-passphrase-file", true); 526 outputEncryptionPassphraseFile.addLongIdentifier("outputPassphraseFile", 527 true); 528 outputEncryptionPassphraseFile.addLongIdentifier("output-passphrase-file", 529 true); 530 outputEncryptionPassphraseFile.addLongIdentifier( 531 "outputEncryptionPasswordFile", true); 532 outputEncryptionPassphraseFile.addLongIdentifier( 533 "output-encryption-password-file", true); 534 outputEncryptionPassphraseFile.addLongIdentifier("outputPasswordFile", 535 true); 536 outputEncryptionPassphraseFile.addLongIdentifier("output-password-file", 537 true); 538 outputEncryptionPassphraseFile.addLongIdentifier( 539 "outputEncryptionPasswordFile", true); 540 outputEncryptionPassphraseFile.addLongIdentifier( 541 "output-encryption-password-file", true); 542 outputEncryptionPassphraseFile.addLongIdentifier("outputPasswordFile", 543 true); 544 outputEncryptionPassphraseFile.addLongIdentifier("output-password-file", 545 true); 546 outputEncryptionPassphraseFile.setArgumentGroupName( 547 INFO_LDIFSEARCH_ARG_GROUP_OUTPUT.get()); 548 parser.addArgument(outputEncryptionPassphraseFile); 549 550 551 overwriteExistingOutputFile = new BooleanArgument('O', 552 "overwriteExistingOutputFile", 1, 553 INFO_LDIFSEARCH_ARG_DESC_OVERWRITE_EXISTING.get()); 554 overwriteExistingOutputFile.addLongIdentifier( 555 "overwrite-existing-output-file", true); 556 overwriteExistingOutputFile.addLongIdentifier( 557 "overwriteExistingOutputFiles", true); 558 overwriteExistingOutputFile.addLongIdentifier( 559 "overwrite-existing-output-files", true); 560 overwriteExistingOutputFile.addLongIdentifier("overwriteExistingOutput", 561 true); 562 overwriteExistingOutputFile.addLongIdentifier("overwrite-existing-output", 563 true); 564 overwriteExistingOutputFile.addLongIdentifier("overwriteExisting", true); 565 overwriteExistingOutputFile.addLongIdentifier("overwrite-existing", true); 566 overwriteExistingOutputFile.addLongIdentifier("overwrite", true); 567 overwriteExistingOutputFile.setArgumentGroupName( 568 INFO_LDIFSEARCH_ARG_GROUP_OUTPUT.get()); 569 parser.addArgument(overwriteExistingOutputFile); 570 571 572 wrapColumn = new IntegerArgument(null, "wrapColumn", false, 1, null, 573 INFO_LDIFSEARCH_ARG_DESC_WRAP_COLUMN.get(), 5, Integer.MAX_VALUE); 574 wrapColumn.addLongIdentifier("wrap-column", true); 575 wrapColumn.setArgumentGroupName(INFO_LDIFSEARCH_ARG_GROUP_OUTPUT.get()); 576 parser.addArgument(wrapColumn); 577 578 579 doNotWrap = new BooleanArgument('T', "doNotWrap", 1, 580 INFO_LDIFSEARCH_ARG_DESC_DO_NOT_WRAP.get()); 581 doNotWrap.addLongIdentifier("do-not-wrap", true); 582 doNotWrap.addLongIdentifier("dontWrap", true); 583 doNotWrap.addLongIdentifier("dont-wrap", true); 584 doNotWrap.addLongIdentifier("noWrap", true); 585 doNotWrap.addLongIdentifier("no-wrap", true); 586 doNotWrap.setArgumentGroupName(INFO_LDIFSEARCH_ARG_GROUP_OUTPUT.get()); 587 parser.addArgument(doNotWrap); 588 589 590 baseDN = new DNArgument('b', "baseDN", false, 1, null, 591 INFO_LDIFSEARCH_ARG_DESC_BASE_DN.get()); 592 baseDN.addLongIdentifier("base-dn", true); 593 baseDN.addLongIdentifier("searchBaseDN", true); 594 baseDN.addLongIdentifier("search-base-dn", true); 595 baseDN.addLongIdentifier("searchBase", true); 596 baseDN.addLongIdentifier("search-base", true); 597 baseDN.addLongIdentifier("base", true); 598 baseDN.setArgumentGroupName(INFO_LDIFSEARCH_ARG_GROUP_CRITERIA.get()); 599 parser.addArgument(baseDN); 600 601 602 scope = new ScopeArgument('s', "scope", false, null, 603 INFO_LDIFSEARCH_ARG_DESC_SCOPE.get()); 604 scope.addLongIdentifier("searchScope", true); 605 scope.addLongIdentifier("search-scope", true); 606 scope.setArgumentGroupName(INFO_LDIFSEARCH_ARG_GROUP_CRITERIA.get()); 607 parser.addArgument(scope); 608 609 610 filterFile = new FileArgument('f', "filterFile", false, 0, null, 611 INFO_LDIFSEARCH_ARG_DESC_FILTER_FILE.get(), true, true, true, false); 612 filterFile.addLongIdentifier("filter-file", true); 613 filterFile.addLongIdentifier("filtersFile", true); 614 filterFile.addLongIdentifier("filters-file", true); 615 filterFile.setArgumentGroupName(INFO_LDIFSEARCH_ARG_GROUP_CRITERIA.get()); 616 parser.addArgument(filterFile); 617 618 619 ldapURLFile = new FileArgument(null, "ldapURLFile", false, 0, null, 620 INFO_LDIFSEARCH_ARG_DESC_LDAP_URL_FILE.get(), true, true, true, false); 621 ldapURLFile.addLongIdentifier("ldap-url-file", true); 622 ldapURLFile.setArgumentGroupName(INFO_LDIFSEARCH_ARG_GROUP_CRITERIA.get()); 623 parser.addArgument(ldapURLFile); 624 625 626 sizeLimit = new IntegerArgument('z', "sizeLimit", false, 1, null, 627 INFO_LDIFSEARCH_ARG_DESC_SIZE_LIMIT.get(), 0, Integer.MAX_VALUE, 0); 628 sizeLimit.addLongIdentifier("size-limit", true); 629 sizeLimit.addLongIdentifier("searchSizeLimit", true); 630 sizeLimit.addLongIdentifier("search-size-limit", true); 631 sizeLimit.setArgumentGroupName(INFO_LDIFSEARCH_ARG_GROUP_CRITERIA.get()); 632 sizeLimit.setHidden(true); 633 parser.addArgument(sizeLimit); 634 635 636 timeLimitSeconds = new IntegerArgument('t', "timeLimitSeconds", false, 1, 637 null, INFO_LDIFSEARCH_ARG_DESC_TIME_LIMIT_SECONDS.get(), 0, 638 Integer.MAX_VALUE, 0); 639 timeLimitSeconds.addLongIdentifier("time-limit-seconds", true); 640 timeLimitSeconds.addLongIdentifier("timeLimit", true); 641 timeLimitSeconds.setArgumentGroupName( 642 INFO_LDIFSEARCH_ARG_GROUP_CRITERIA.get()); 643 timeLimitSeconds.setHidden(true); 644 parser.addArgument(timeLimitSeconds); 645 646 647 parser.addDependentArgumentSet(separateOutputFilePerSearch, outputFile); 648 parser.addDependentArgumentSet(compressOutput, outputFile); 649 parser.addDependentArgumentSet(encryptOutput, outputFile); 650 parser.addDependentArgumentSet(overwriteExistingOutputFile, outputFile); 651 parser.addDependentArgumentSet(outputEncryptionPassphraseFile, 652 encryptOutput); 653 654 parser.addExclusiveArgumentSet(wrapColumn, doNotWrap); 655 parser.addExclusiveArgumentSet(baseDN, ldapURLFile); 656 parser.addExclusiveArgumentSet(scope, ldapURLFile); 657 parser.addExclusiveArgumentSet(filterFile, ldapURLFile); 658 } 659 660 661 662 /** 663 * {@inheritDoc} 664 */ 665 @Override() 666 public void doExtendedArgumentValidation() 667 throws ArgumentException 668 { 669 // If the output file exists and either compressOutput or encryptOutput is 670 // present, then the overwrite argument must also be present. 671 final File outFile = outputFile.getValue(); 672 if ((outFile != null) && outFile.exists() && 673 (compressOutput.isPresent() || encryptOutput.isPresent()) && 674 (! overwriteExistingOutputFile.isPresent())) 675 { 676 throw new ArgumentException( 677 ERR_LDIFSEARCH_APPEND_WITH_COMPRESSION_OR_ENCRYPTION.get( 678 compressOutput.getIdentifierString(), 679 encryptOutput.getIdentifierString(), 680 overwriteExistingOutputFile.getIdentifierString())); 681 } 682 683 684 // Create the set of LDAP URLs to use when issuing the searches. 685 final List<String> trailingArgs = parser.getTrailingArguments(); 686 if (filterFile.isPresent()) 687 { 688 // If there are trailing arguments, then make sure the first one is not a 689 // valid filter. 690 if (! trailingArgs.isEmpty()) 691 { 692 try 693 { 694 Filter.create(trailingArgs.get(0)); 695 throw new ArgumentException( 696 ERR_LDIFSEARCH_FILTER_FILE_WITH_TRAILING_FILTER.get()); 697 } 698 catch (final LDAPException e) 699 { 700 // This was expected. 701 } 702 } 703 704 readFilterFile(); 705 } 706 else if (ldapURLFile.isPresent()) 707 { 708 // Make sure there aren't any trailing arguments. 709 if (! trailingArgs.isEmpty()) 710 { 711 throw new ArgumentException( 712 ERR_LDIFSEARCH_LDAP_URL_FILE_WITH_TRAILING_ARGS.get()); 713 } 714 715 readLDAPURLFile(); 716 717 718 // If there are multiple LDAP URLs, and if they should not be sent to 719 // separate output files, then they must all have the same set of 720 // requested attributes. 721 if ((searchURLs.size() > 1) && 722 (! separateOutputFilePerSearch.isPresent())) 723 { 724 final Iterator<LDAPURL> iterator = searchURLs.iterator(); 725 final Set<String> requestedAttrs = 726 new HashSet<>(Arrays.asList(iterator.next().getAttributes())); 727 while (iterator.hasNext()) 728 { 729 final Set<String> attrSet = new HashSet<>(Arrays.asList( 730 iterator.next().getAttributes())); 731 if (! requestedAttrs.equals(attrSet)) 732 { 733 throw new ArgumentException( 734 ERR_LDIFSEARCH_DIFFERENT_URL_ATTRS_IN_SAME_FILE.get( 735 ldapURLFile.getIdentifierString(), 736 separateOutputFilePerSearch.getIdentifierString())); 737 } 738 } 739 } 740 } 741 else 742 { 743 // Make sure there is at least one trailing argument, and that it's a 744 // valid filter. If there are any others, then they must be the 745 // requested arguments. 746 if (trailingArgs.isEmpty()) 747 { 748 throw new ArgumentException(ERR_LDIFSEARCH_NO_FILTER.get()); 749 } 750 751 752 final Filter filter; 753 final String[] requestedAttributes; 754 try 755 { 756 final List<String> trailingArgList = new ArrayList<>(trailingArgs); 757 filter = Filter.create(trailingArgList.remove(0)); 758 requestedAttributes = trailingArgList.toArray(StaticUtils.NO_STRINGS); 759 } 760 catch (final LDAPException e) 761 { 762 Debug.debugException(e); 763 throw new ArgumentException( 764 ERR_LDIFSEARCH_FIRST_TRAILING_ARG_NOT_FILTER.get(), e); 765 } 766 767 768 DN dn = baseDN.getValue(); 769 if (dn == null) 770 { 771 dn = DN.NULL_DN; 772 } 773 774 SearchScope searchScope = scope.getValue(); 775 if (searchScope == null) 776 { 777 searchScope = SearchScope.SUB; 778 } 779 780 try 781 { 782 searchURLs.add(new LDAPURL("ldap", null, null, dn, requestedAttributes, 783 searchScope, filter)); 784 } 785 catch (final LDAPException e) 786 { 787 Debug.debugException(e); 788 // This should never happen. 789 throw new ArgumentException(StaticUtils.getExceptionMessage(e), e); 790 } 791 } 792 } 793 794 795 796 /** 797 * Uses the contents of any specified filter files, along with the configured 798 * base DN, scope, and requested attributes, to populate the set of search 799 * URLs. 800 * 801 * @throws ArgumentException If a problem is encountered while constructing 802 * the search URLs. 803 */ 804 private void readFilterFile() 805 throws ArgumentException 806 { 807 DN dn = baseDN.getValue(); 808 if (dn == null) 809 { 810 dn = DN.NULL_DN; 811 } 812 813 SearchScope searchScope = scope.getValue(); 814 if (searchScope == null) 815 { 816 searchScope = SearchScope.SUB; 817 } 818 819 final String[] requestedAttributes = 820 parser.getTrailingArguments().toArray(StaticUtils.NO_STRINGS); 821 822 for (final File f : filterFile.getValues()) 823 { 824 final InputStream inputStream; 825 try 826 { 827 inputStream = openInputStream(f); 828 } 829 catch (final LDAPException e) 830 { 831 Debug.debugException(e); 832 throw new ArgumentException(e.getMessage(), e); 833 } 834 835 try (BufferedReader reader = 836 new BufferedReader(new InputStreamReader(inputStream))) 837 { 838 while (true) 839 { 840 final String line = reader.readLine(); 841 if (line == null) 842 { 843 break; 844 } 845 846 if (line.isEmpty() || line.startsWith("#")) 847 { 848 continue; 849 } 850 851 try 852 { 853 final Filter filter = Filter.create(line.trim()); 854 searchURLs.add(new LDAPURL("ldap", null, null, dn, 855 requestedAttributes, searchScope, filter)); 856 } 857 catch (final LDAPException e) 858 { 859 Debug.debugException(e); 860 throw new ArgumentException( 861 ERR_LDIFSEARCH_FILTER_FILE_INVALID_FILTER.get(line, 862 f.getAbsolutePath(), e.getMessage()), 863 e); 864 } 865 } 866 } 867 catch (final IOException e) 868 { 869 Debug.debugException(e); 870 throw new ArgumentException( 871 ERR_LDIFSEARCH_ERROR_READING_FILTER_FILE.get(f.getAbsolutePath(), 872 StaticUtils.getExceptionMessage(e)), 873 e); 874 } 875 finally 876 { 877 try 878 { 879 inputStream.close(); 880 } 881 catch (final Exception e) 882 { 883 Debug.debugException(e); 884 } 885 } 886 887 } 888 889 if (searchURLs.isEmpty()) 890 { 891 throw new ArgumentException(ERR_LDIFSEARCH_NO_FILTERS_FROM_FILE.get( 892 filterFile.getValues().get(0).getAbsolutePath())); 893 } 894 } 895 896 897 898 /** 899 * Uses the contents of any specified LDAP URL files to populate the set of 900 * search URLs. 901 * 902 * @throws ArgumentException If a problem is encountered while constructing 903 * the search URLs. 904 */ 905 private void readLDAPURLFile() 906 throws ArgumentException 907 { 908 for (final File f : ldapURLFile.getValues()) 909 { 910 final InputStream inputStream; 911 try 912 { 913 inputStream = openInputStream(f); 914 } 915 catch (final LDAPException e) 916 { 917 Debug.debugException(e); 918 throw new ArgumentException(e.getMessage(), e); 919 } 920 921 try (BufferedReader reader = 922 new BufferedReader(new InputStreamReader(inputStream))) 923 { 924 while (true) 925 { 926 final String line = reader.readLine(); 927 if (line == null) 928 { 929 break; 930 } 931 932 if (line.isEmpty() || line.startsWith("#")) 933 { 934 continue; 935 } 936 937 try 938 { 939 searchURLs.add(new LDAPURL(line.trim())); 940 } 941 catch (final LDAPException e) 942 { 943 Debug.debugException(e); 944 throw new ArgumentException( 945 ERR_LDIFSEARCH_LDAP_URL_FILE_INVALID_URL.get(line, 946 f.getAbsolutePath(), e.getMessage()), 947 e); 948 } 949 } 950 } 951 catch (final IOException e) 952 { 953 Debug.debugException(e); 954 throw new ArgumentException( 955 ERR_LDIFSEARCH_ERROR_READING_LDAP_URL_FILE.get(f.getAbsolutePath(), 956 StaticUtils.getExceptionMessage(e)), 957 e); 958 } 959 finally 960 { 961 try 962 { 963 inputStream.close(); 964 } 965 catch (final Exception e) 966 { 967 Debug.debugException(e); 968 } 969 } 970 } 971 972 if (searchURLs.isEmpty()) 973 { 974 throw new ArgumentException(ERR_LDIFSEARCH_NO_URLS_FROM_FILE.get( 975 ldapURLFile.getValues().get(0).getAbsolutePath())); 976 } 977 } 978 979 980 981 /** 982 * {@inheritDoc} 983 */ 984 @Override() 985 @NotNull() 986 public ResultCode doToolProcessing() 987 { 988 // Get the schema to use when performing LDIF processing. 989 final Schema schema; 990 try 991 { 992 if (schemaPath.isPresent()) 993 { 994 schema = getSchema(schemaPath.getValues()); 995 } 996 else if (PING_SERVER_AVAILABLE) 997 { 998 schema = getSchema(Collections.singletonList(StaticUtils.constructPath( 999 PING_SERVER_ROOT, "config", "schema"))); 1000 } 1001 else 1002 { 1003 schema = Schema.getDefaultStandardSchema(); 1004 } 1005 } 1006 catch (final Exception e) 1007 { 1008 Debug.debugException(e); 1009 logCompletionMessage(true, 1010 ERR_LDIFSEARCH_CANNOT_GET_SCHEMA.get( 1011 StaticUtils.getExceptionMessage(e))); 1012 return ResultCode.LOCAL_ERROR; 1013 } 1014 1015 1016 // Create search entry parers for all of the search URLs. 1017 final Map<LDAPURL,SearchEntryParer> urlMap = new LinkedHashMap<>(); 1018 for (final LDAPURL url : searchURLs) 1019 { 1020 final SearchEntryParer parer = new SearchEntryParer( 1021 Arrays.asList(url.getAttributes()), schema); 1022 urlMap.put(url, parer); 1023 } 1024 1025 1026 // If we should check schema, then create the entry validator. 1027 final EntryValidator entryValidator; 1028 if (checkSchema.isPresent()) 1029 { 1030 entryValidator = new EntryValidator(schema); 1031 } 1032 else 1033 { 1034 entryValidator = null; 1035 } 1036 1037 1038 // Create the output files, if appropriate. 1039 boolean closewriter = true; 1040 LDIFWriter singleWriter = null; 1041 SearchEntryParer singleParer = null; 1042 final Map<LDAPURL,LDIFSearchSeparateSearchDetails> separateWriters = 1043 new LinkedHashMap<>(); 1044 try 1045 { 1046 if (outputFile.isPresent()) 1047 { 1048 final int numURLs = searchURLs.size(); 1049 if (separateOutputFilePerSearch.isPresent() && (numURLs > 1)) 1050 { 1051 int i=1; 1052 for (final LDAPURL url : searchURLs) 1053 { 1054 final File f = new 1055 File(outputFile.getValue().getAbsolutePath() + '.' + i); 1056 final LDIFSearchSeparateSearchDetails details = 1057 new LDIFSearchSeparateSearchDetails(url, f, 1058 createLDIFWriter(f, url), schema); 1059 separateWriters.put(url, details); 1060 i++; 1061 } 1062 } 1063 else 1064 { 1065 singleWriter = createLDIFWriter(outputFile.getValue(), null); 1066 } 1067 } 1068 else 1069 { 1070 singleWriter = new LDIFWriter(getOut()); 1071 closewriter = false; 1072 } 1073 1074 1075 // Iterate through the LDIF files and process the entries they contain. 1076 boolean errorEncountered = false; 1077 final List<LDAPURL> matchingURLs = new ArrayList<>(); 1078 final List<String> entryInvalidReasons = new ArrayList<>(); 1079 for (final File f : ldifFile.getValues()) 1080 { 1081 final LDIFReader ldifReader; 1082 try 1083 { 1084 ldifReader = new LDIFReader(openInputStream(f)); 1085 1086 if (stripTrailingSpaces.isPresent()) 1087 { 1088 ldifReader.setTrailingSpaceBehavior(TrailingSpaceBehavior.STRIP); 1089 } 1090 else 1091 { 1092 ldifReader.setTrailingSpaceBehavior(TrailingSpaceBehavior.REJECT); 1093 } 1094 } 1095 catch (final Exception e) 1096 { 1097 Debug.debugException(e); 1098 logCompletionMessage(true, 1099 ERR_LDIFSEARCH_CANNOT_OPEN_LDIF_FILE.get(f.getName(), 1100 StaticUtils.getExceptionMessage(e))); 1101 return ResultCode.LOCAL_ERROR; 1102 } 1103 1104 try 1105 { 1106 while (true) 1107 { 1108 final Entry entry; 1109 try 1110 { 1111 entry = ldifReader.readEntry(); 1112 } 1113 catch (final LDIFException e) 1114 { 1115 Debug.debugException(e); 1116 if (e.mayContinueReading()) 1117 { 1118 commentToErr(ERR_LDIFSEARCH_RECOVERABLE_READ_ERROR.get( 1119 f.getAbsolutePath(), StaticUtils.getExceptionMessage(e))); 1120 errorEncountered = true; 1121 continue; 1122 } 1123 else 1124 { 1125 logCompletionMessage(true, 1126 ERR_LDIFSEARCH_UNRECOVERABLE_READ_ERROR.get( 1127 f.getAbsolutePath(), 1128 StaticUtils.getExceptionMessage(e))); 1129 return ResultCode.LOCAL_ERROR; 1130 } 1131 } 1132 catch (final Exception e) 1133 { 1134 logCompletionMessage(true, 1135 ERR_LDIFSEARCH_UNRECOVERABLE_READ_ERROR.get( 1136 f.getAbsolutePath(), 1137 StaticUtils.getExceptionMessage(e))); 1138 return ResultCode.LOCAL_ERROR; 1139 } 1140 1141 if (entry == null) 1142 { 1143 break; 1144 } 1145 1146 if (entryValidator != null) 1147 { 1148 entryInvalidReasons.clear(); 1149 if (! entryValidator.entryIsValid(entry, entryInvalidReasons)) 1150 { 1151 commentToErr(ERR_LDIFSEARCH_ENTRY_VIOLATES_SCHEMA.get( 1152 entry.getDN())); 1153 for (final String invalidReason : entryInvalidReasons) 1154 { 1155 commentToErr("- " + invalidReason); 1156 } 1157 1158 err(); 1159 errorEncountered = true; 1160 continue; 1161 } 1162 } 1163 1164 if (singleWriter != null) 1165 { 1166 matchingURLs.clear(); 1167 for (final LDAPURL url : searchURLs) 1168 { 1169 if (urlMatchesEntry(url, entry)) 1170 { 1171 matchingURLs.add(url); 1172 } 1173 } 1174 1175 if (matchingURLs.isEmpty()) 1176 { 1177 continue; 1178 } 1179 1180 try 1181 { 1182 if (searchURLs.size() > 1) 1183 { 1184 singleWriter.writeComment( 1185 INFO_LDIFSEARCH_ENTRY_MATCHES_URLS.get(entry.getDN()), 1186 false, false); 1187 for (final LDAPURL url : matchingURLs) 1188 { 1189 singleWriter.writeComment(url.toString(), false, false); 1190 } 1191 } 1192 1193 if (singleParer == null) 1194 { 1195 singleParer = new SearchEntryParer( 1196 Arrays.asList(searchURLs.get(0).getAttributes()), 1197 schema); 1198 } 1199 1200 final Entry paredEntry = singleParer.pareEntry(entry); 1201 singleWriter.writeEntry(paredEntry); 1202 1203 if (! outputFile.isPresent()) 1204 { 1205 singleWriter.flush(); 1206 } 1207 } 1208 catch (final Exception e) 1209 { 1210 Debug.debugException(e); 1211 if (outputFile.isPresent()) 1212 { 1213 logCompletionMessage(true, 1214 ERR_LDIFSEARCH_WRITE_ERROR_WITH_FILE.get(entry.getDN(), 1215 outputFile.getValue().getAbsolutePath(), 1216 StaticUtils.getExceptionMessage(e))); 1217 } 1218 else 1219 { 1220 logCompletionMessage(true, 1221 ERR_LDIFSEARCH_WRITE_ERROR_NO_FILE.get(entry.getDN(), 1222 StaticUtils.getExceptionMessage(e))); 1223 } 1224 return ResultCode.LOCAL_ERROR; 1225 } 1226 } 1227 else 1228 { 1229 for (final LDIFSearchSeparateSearchDetails details : 1230 separateWriters.values()) 1231 { 1232 final LDAPURL url = details.getLDAPURL(); 1233 if (urlMatchesEntry(url, entry)) 1234 { 1235 try 1236 { 1237 final Entry paredEntry = 1238 details.getSearchEntryParer().pareEntry(entry); 1239 details.getLDIFWriter().writeEntry(paredEntry); 1240 } 1241 catch (final Exception ex) 1242 { 1243 Debug.debugException(ex); 1244 logCompletionMessage(true, 1245 ERR_LDIFSEARCH_WRITE_ERROR_WITH_FILE.get(entry.getDN(), 1246 details.getOutputFile().getAbsolutePath(), 1247 StaticUtils.getExceptionMessage(ex))); 1248 return ResultCode.LOCAL_ERROR; 1249 } 1250 } 1251 } 1252 } 1253 } 1254 } 1255 finally 1256 { 1257 try 1258 { 1259 ldifReader.close(); 1260 } 1261 catch (final Exception e) 1262 { 1263 Debug.debugException(e); 1264 } 1265 } 1266 } 1267 1268 if (errorEncountered) 1269 { 1270 logCompletionMessage(true, 1271 WARN_LDIFSEARCH_COMPLETED_WITH_ERRORS.get()); 1272 return ResultCode.PARAM_ERROR; 1273 } 1274 else 1275 { 1276 logCompletionMessage(false, 1277 INFO_LDIFSEARCH_COMPLETED_SUCCESSFULLY.get()); 1278 return ResultCode.SUCCESS; 1279 } 1280 } 1281 catch (final LDAPException e) 1282 { 1283 Debug.debugException(e); 1284 logCompletionMessage(true, e.getMessage()); 1285 return e.getResultCode(); 1286 } 1287 finally 1288 { 1289 if (singleWriter != null) 1290 { 1291 try 1292 { 1293 singleWriter.flush(); 1294 1295 if (closewriter) 1296 { 1297 singleWriter.close(); 1298 } 1299 } 1300 catch (final Exception e) 1301 { 1302 Debug.debugException(e); 1303 } 1304 } 1305 1306 for (final LDIFSearchSeparateSearchDetails details : 1307 separateWriters.values()) 1308 { 1309 try 1310 { 1311 details.getLDIFWriter().close(); 1312 } 1313 catch (final Exception e) 1314 { 1315 Debug.debugException(e); 1316 } 1317 } 1318 } 1319 } 1320 1321 1322 1323 /** 1324 * Retrieves the schema contained in the specified paths. 1325 * 1326 * @param paths The paths to use to access the schema. 1327 * 1328 * @return The schema read from the specified files. 1329 * 1330 * @throws Exception If a problem is encountered while loading the schema. 1331 */ 1332 @NotNull() 1333 private static Schema getSchema(@NotNull final List<File> paths) 1334 throws Exception 1335 { 1336 final Set<File> schemaFiles = new LinkedHashSet<>(); 1337 for (final File f : paths) 1338 { 1339 if (f.exists()) 1340 { 1341 if (f.isFile()) 1342 { 1343 schemaFiles.add(f); 1344 } 1345 else if (f.isDirectory()) 1346 { 1347 final TreeMap<String,File> sortedFiles = new TreeMap<>(); 1348 for (final File fileInDir : f.listFiles()) 1349 { 1350 if (fileInDir.isFile()) 1351 { 1352 sortedFiles.put(fileInDir.getName(), fileInDir); 1353 } 1354 } 1355 1356 schemaFiles.addAll(sortedFiles.values()); 1357 } 1358 } 1359 } 1360 1361 return Schema.getSchema(new ArrayList<>(schemaFiles)); 1362 } 1363 1364 1365 1366 /** 1367 * Opens the input stream to use to read from the specified file. 1368 * 1369 * @param f The file for which to open the input stream. It may optionally 1370 * be compressed and/or encrypted. 1371 * 1372 * @return The input stream that was created. 1373 * 1374 * @throws LDAPException If a problem is encountered while opening the file. 1375 */ 1376 @NotNull() 1377 private InputStream openInputStream(@NotNull final File f) 1378 throws LDAPException 1379 { 1380 if (ldifEncryptionPassphraseFile.isPresent() && 1381 (! ldifEncryptionPassphraseFileRead)) 1382 { 1383 readPassphraseFile(ldifEncryptionPassphraseFile.getValue()); 1384 ldifEncryptionPassphraseFileRead = true; 1385 } 1386 1387 1388 boolean closeStream = true; 1389 InputStream inputStream = null; 1390 try 1391 { 1392 inputStream = new FileInputStream(f); 1393 1394 final ObjectPair<InputStream,char[]> p = 1395 ToolUtils.getPossiblyPassphraseEncryptedInputStream( 1396 inputStream, inputEncryptionPassphrases, 1397 (! ldifEncryptionPassphraseFile.isPresent()), 1398 INFO_LDIFSEARCH_ENTER_ENCRYPTION_PW.get(f.getName()), 1399 ERR_LDIFSEARCH_WRONG_ENCRYPTION_PW.get(), getOut(), getErr()); 1400 inputStream = p.getFirst(); 1401 addPassphrase(p.getSecond()); 1402 1403 inputStream = ToolUtils.getPossiblyGZIPCompressedInputStream(inputStream); 1404 closeStream = false; 1405 return inputStream; 1406 } 1407 catch (final Exception e) 1408 { 1409 Debug.debugException(e); 1410 throw new LDAPException(ResultCode.LOCAL_ERROR, 1411 ERR_LDIFSEARCH_ERROR_OPENING_INPUT_FILE.get(f.getAbsolutePath(), 1412 StaticUtils.getExceptionMessage(e)), 1413 e); 1414 } 1415 finally 1416 { 1417 if ((inputStream != null) && closeStream) 1418 { 1419 try 1420 { 1421 inputStream.close(); 1422 } 1423 catch (final Exception e) 1424 { 1425 Debug.debugException(e); 1426 } 1427 } 1428 } 1429 } 1430 1431 1432 1433 /** 1434 * Reads the contents of the specified passphrase file and adds it to the list 1435 * of passphrases. 1436 * 1437 * @param f The passphrase file to read. 1438 * 1439 * @throws LDAPException If a problem is encountered while trying to read 1440 * the passphrase from the provided file. 1441 */ 1442 private void readPassphraseFile(@NotNull final File f) 1443 throws LDAPException 1444 { 1445 try 1446 { 1447 addPassphrase(getPasswordFileReader().readPassword(f)); 1448 } 1449 catch (final Exception e) 1450 { 1451 Debug.debugException(e); 1452 throw new LDAPException(ResultCode.LOCAL_ERROR, 1453 ERR_LDIFSEARCH_CANNOT_READ_PW_FILE.get(f.getAbsolutePath(), 1454 StaticUtils.getExceptionMessage(e)), 1455 e); 1456 } 1457 } 1458 1459 1460 1461 /** 1462 * Updates the list of encryption passphrases with the provided passphrase, if 1463 * it is not already present. 1464 * 1465 * @param passphrase The passphrase to be added. It may optionally be 1466 * {@code null} (in which case no action will be taken). 1467 */ 1468 private void addPassphrase(@Nullable final char[] passphrase) 1469 { 1470 if (passphrase == null) 1471 { 1472 return; 1473 } 1474 1475 for (final char[] existingPassphrase : inputEncryptionPassphrases) 1476 { 1477 if (Arrays.equals(existingPassphrase, passphrase)) 1478 { 1479 return; 1480 } 1481 } 1482 1483 inputEncryptionPassphrases.add(passphrase); 1484 } 1485 1486 1487 1488 /** 1489 * Creates an LDIF writer to write to the specified file. 1490 * 1491 * @param f The file to be written. 1492 * @param ldapURL The LDAP URL with which the file will be associated. It 1493 * may be {@code null} if the file is shared across multiple 1494 * URLs. 1495 * 1496 * @return The LDIF writer that was created. 1497 * 1498 * @throws LDAPException If a problem occurs while creating the LDIF writer. 1499 */ 1500 @NotNull() 1501 private LDIFWriter createLDIFWriter(@NotNull final File f, 1502 @Nullable final LDAPURL ldapURL) 1503 throws LDAPException 1504 { 1505 OutputStream outputStream = null; 1506 boolean closeOutputStream = true; 1507 try 1508 { 1509 try 1510 { 1511 1512 outputStream = new FileOutputStream(f, 1513 (! overwriteExistingOutputFile.isPresent())); 1514 } 1515 catch (final Exception e) 1516 { 1517 Debug.debugException(e); 1518 throw new LDAPException(ResultCode.LOCAL_ERROR, 1519 ERR_LDIFSEARCH_CANNOT_OPEN_OUTPUT_FILE.get(f.getAbsolutePath(), 1520 StaticUtils.getExceptionMessage(e)), 1521 e); 1522 } 1523 1524 if (encryptOutput.isPresent()) 1525 { 1526 try 1527 { 1528 final char[] passphrase; 1529 if (outputEncryptionPassphraseFile.isPresent()) 1530 { 1531 passphrase = getPasswordFileReader().readPassword( 1532 outputEncryptionPassphraseFile.getValue()); 1533 } 1534 else 1535 { 1536 passphrase = ToolUtils.promptForEncryptionPassphrase(false, true, 1537 INFO_LDIFSEARCH_PROMPT_OUTPUT_FILE_ENC_PW.get(), 1538 INFO_LDIFSEARCH_CONFIRM_OUTPUT_FILE_ENC_PW.get(), getOut(), 1539 getErr()).toCharArray(); 1540 } 1541 1542 outputStream = new PassphraseEncryptedOutputStream(passphrase, 1543 outputStream, null, true, true); 1544 } 1545 catch (final Exception e) 1546 { 1547 Debug.debugException(e); 1548 throw new LDAPException(ResultCode.LOCAL_ERROR, 1549 ERR_LDIFSEARCH_CANNOT_ENCRYPT_OUTPUT_FILE.get( 1550 StaticUtils.getExceptionMessage(e)), 1551 e); 1552 } 1553 } 1554 1555 if (compressOutput.isPresent()) 1556 { 1557 try 1558 { 1559 outputStream = new GZIPOutputStream(outputStream); 1560 } 1561 catch (final Exception e) 1562 { 1563 Debug.debugException(e); 1564 throw new LDAPException(ResultCode.LOCAL_ERROR, 1565 ERR_LDIFSEARCH_CANNOT_COMPRESS_OUTPUT_FILE.get( 1566 f.getAbsolutePath(), StaticUtils.getExceptionMessage(e)), 1567 e); 1568 } 1569 } 1570 1571 final LDIFWriter ldifWriter = new LDIFWriter(outputStream); 1572 if (doNotWrap.isPresent()) 1573 { 1574 ldifWriter.setWrapColumn(0); 1575 } 1576 else if (wrapColumn.isPresent()) 1577 { 1578 ldifWriter.setWrapColumn(wrapColumn.getValue()); 1579 } 1580 else 1581 { 1582 ldifWriter.setWrapColumn(WRAP_COLUMN); 1583 } 1584 1585 if (ldapURL != null) 1586 { 1587 try 1588 { 1589 ldifWriter.writeComment( 1590 INFO_LDIFSEARCH_ENTRIES_MATCHING_URL.get(ldapURL.toString()), 1591 false, true); 1592 } 1593 catch (final Exception e) 1594 { 1595 Debug.debugException(e); 1596 } 1597 } 1598 1599 closeOutputStream = false; 1600 return ldifWriter; 1601 } 1602 finally 1603 { 1604 if (closeOutputStream && (outputStream != null)) 1605 { 1606 try 1607 { 1608 outputStream.close(); 1609 } 1610 catch (final Exception e) 1611 { 1612 Debug.debugException(e); 1613 } 1614 } 1615 } 1616 } 1617 1618 1619 1620 /** 1621 * Indicates whether the given entry matches the criteria in the provided LDAP 1622 * URL. 1623 * 1624 * @param url The URL for which to make the determination. 1625 * @param entry The entry for which to make the determination. 1626 * 1627 * @return {@code true} if the entry matches the criteria in the LDAP URL, or 1628 * {@code false} if not. 1629 */ 1630 private boolean urlMatchesEntry(@NotNull final LDAPURL url, 1631 @NotNull final Entry entry) 1632 { 1633 try 1634 { 1635 return (entry.matchesBaseAndScope(url.getBaseDN(), url.getScope()) && 1636 url.getFilter().matchesEntry(entry)); 1637 } 1638 catch (final Exception e) 1639 { 1640 Debug.debugException(e); 1641 return false; 1642 } 1643 } 1644 1645 1646 1647 /** 1648 * Writes a line-wrapped, commented version of the provided message to 1649 * standard output. 1650 * 1651 * @param message The message to be written. 1652 */ 1653 private void commentToOut(@NotNull final String message) 1654 { 1655 getOut().flush(); 1656 for (final String line : StaticUtils.wrapLine(message, (WRAP_COLUMN - 2))) 1657 { 1658 out("# " + line); 1659 } 1660 getOut().flush(); 1661 } 1662 1663 1664 1665 /** 1666 * Writes a line-wrapped, commented version of the provided message to 1667 * standard error. 1668 * 1669 * @param message The message to be written. 1670 */ 1671 private void commentToErr(@NotNull final String message) 1672 { 1673 getErr().flush(); 1674 for (final String line : StaticUtils.wrapLine(message, (WRAP_COLUMN - 2))) 1675 { 1676 err("# " + line); 1677 } 1678 getErr().flush(); 1679 } 1680 1681 1682 1683 /** 1684 * Writes the provided message and sets it as the completion message. 1685 * 1686 * @param isError Indicates whether the message should be written to 1687 * standard error rather than standard output. 1688 * @param message The message to be written. 1689 */ 1690 private void logCompletionMessage(final boolean isError, 1691 @NotNull final String message) 1692 { 1693 completionMessage.compareAndSet(null, message); 1694 1695 if (isError) 1696 { 1697 commentToErr(message); 1698 } 1699 else 1700 { 1701 commentToOut(message); 1702 } 1703 } 1704 1705 1706 1707 /** 1708 * {@inheritDoc} 1709 */ 1710 @Override() 1711 @NotNull() 1712 public LinkedHashMap<String[],String> getExampleUsages() 1713 { 1714 final LinkedHashMap<String[],String> examples = new LinkedHashMap<>(); 1715 1716 examples.put( 1717 new String[] 1718 { 1719 "--ldifFile", "data.ldif", 1720 "(uid=jdoe)" 1721 }, 1722 INFO_LDIFSEARCH_EXAMPLE_1.get()); 1723 1724 examples.put( 1725 new String[] 1726 { 1727 "--ldifFile", "data.ldif", 1728 "--outputFile", "people.ldif", 1729 "--baseDN", "dc=example,dc=com", 1730 "--scope", "sub", 1731 "(objectClass=person)", 1732 "givenName", 1733 "sn", 1734 "cn", 1735 }, 1736 INFO_LDIFSEARCH_EXAMPLE_2.get()); 1737 1738 return examples; 1739 } 1740}