001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2006-2010 Sun Microsystems, Inc.
025 *      Portions Copyright 2012-2015 ForgeRock AS.
026 */
027package org.opends.server.tools;
028
029import static com.forgerock.opendj.cli.ArgumentConstants.*;
030import static com.forgerock.opendj.cli.Utils.*;
031
032import static org.opends.messages.ToolMessages.*;
033import static org.opends.server.protocols.ldap.LDAPConstants.*;
034import static org.opends.server.protocols.ldap.LDAPResultCode.*;
035import static org.opends.server.util.ServerConstants.*;
036import static org.opends.server.util.StaticUtils.*;
037import static org.opends.server.util.args.LDAPConnectionArgumentParser.*;
038
039import java.io.BufferedReader;
040import java.io.FileReader;
041import java.io.IOException;
042import java.io.OutputStream;
043import java.io.PrintStream;
044import java.util.*;
045import java.util.concurrent.atomic.AtomicInteger;
046
047import org.forgerock.i18n.LocalizableMessage;
048import org.forgerock.i18n.slf4j.LocalizedLogger;
049import org.forgerock.opendj.ldap.ByteString;
050import org.forgerock.opendj.ldap.DecodeException;
051import org.opends.server.controls.*;
052import org.opends.server.core.DirectoryServer.DirectoryServerVersionHandler;
053import org.opends.server.protocols.ldap.*;
054import org.opends.server.types.*;
055import org.opends.server.util.Base64;
056import org.opends.server.util.EmbeddedUtils;
057
058import com.forgerock.opendj.cli.ArgumentException;
059import com.forgerock.opendj.cli.ArgumentParser;
060import com.forgerock.opendj.cli.BooleanArgument;
061import com.forgerock.opendj.cli.CliConstants;
062import com.forgerock.opendj.cli.CommonArguments;
063import com.forgerock.opendj.cli.FileBasedArgument;
064import com.forgerock.opendj.cli.IntegerArgument;
065import com.forgerock.opendj.cli.MultiChoiceArgument;
066import com.forgerock.opendj.cli.StringArgument;
067
068/**
069 * This class provides a tool that can be used to issue search requests to the
070 * Directory Server.
071 */
072public class LDAPSearch
073{
074  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
075
076  /** The fully-qualified name of this class. */
077  private static final String CLASS_NAME = "org.opends.server.tools.LDAPSearch";
078
079
080
081  /** The set of response controls for the search. */
082  private List<Control> responseControls;
083
084  /** The message ID counter to use for requests. */
085  private final AtomicInteger nextMessageID;
086
087  /** The print stream to use for standard error. */
088  private final PrintStream err;
089  /** The print stream to use for standard output. */
090  private final PrintStream out;
091
092
093
094  /**
095   * Constructor for the LDAPSearch object.
096   *
097   * @param  nextMessageID  The message ID counter to use for requests.
098   * @param  out            The print stream to use for standard output.
099   * @param  err            The print stream to use for standard error.
100   */
101  public LDAPSearch(AtomicInteger nextMessageID, PrintStream out,
102                    PrintStream err)
103  {
104    this.nextMessageID = nextMessageID;
105    this.out           = out;
106    this.err           = err;
107    responseControls   = new ArrayList<>();
108  }
109
110
111  /**
112   * Execute the search based on the specified input parameters.
113   *
114   * @param connection     The connection to use for the search.
115   * @param baseDN         The base DN for the search request.
116   * @param filters        The filters to use for the results.
117   * @param attributes     The attributes to return in the results.
118   * @param searchOptions  The constraints for the search.
119   * @param wrapColumn     The column at which to wrap long lines.
120   *
121   * @return  The number of matching entries returned by the server.  If there
122   *          were multiple search filters provided, then this will be the
123   *          total number of matching entries for all searches.
124   *
125   * @throws  IOException  If a problem occurs while attempting to communicate
126   *                       with the Directory Server.
127   *
128   * @throws  LDAPException  If the Directory Server returns an error response.
129   */
130  public int executeSearch(LDAPConnection connection, String baseDN,
131                           ArrayList<LDAPFilter> filters,
132                           LinkedHashSet<String> attributes,
133                           LDAPSearchOptions searchOptions,
134                           int wrapColumn )
135         throws IOException, LDAPException
136  {
137    int matchingEntries = 0;
138
139    for (LDAPFilter filter: filters)
140    {
141      ByteString asn1OctetStr = ByteString.valueOfUtf8(baseDN);
142
143      SearchRequestProtocolOp protocolOp =
144        new SearchRequestProtocolOp(asn1OctetStr,
145                                    searchOptions.getSearchScope(),
146                                    searchOptions.getDereferencePolicy(),
147                                    searchOptions.getSizeLimit(),
148                                    searchOptions.getTimeLimit(),
149                                    searchOptions.getTypesOnly(),
150                                    filter, attributes);
151      if(!searchOptions.showOperations())
152      {
153        try
154        {
155          boolean typesOnly = searchOptions.getTypesOnly();
156          LDAPMessage message = new LDAPMessage(nextMessageID.getAndIncrement(),
157                                                protocolOp,
158                                                searchOptions.getControls());
159          connection.getLDAPWriter().writeMessage(message);
160
161          byte opType;
162          do
163          {
164            int resultCode = 0;
165            LocalizableMessage errorMessage = null;
166            DN matchedDN = null;
167            LDAPMessage responseMessage =
168                 connection.getLDAPReader().readMessage();
169            responseControls = responseMessage.getControls();
170
171
172            opType = responseMessage.getProtocolOpType();
173            switch(opType)
174            {
175              case OP_TYPE_SEARCH_RESULT_ENTRY:
176                for (Control c : responseControls)
177                {
178                  if (c.getOID().equals(OID_ENTRY_CHANGE_NOTIFICATION))
179                  {
180                    try
181                    {
182                      EntryChangeNotificationControl ecn =
183                        EntryChangeNotificationControl.DECODER
184                        .decode(c.isCritical(), ((LDAPControl) c).getValue());
185
186                      out.println(INFO_LDAPSEARCH_PSEARCH_CHANGE_TYPE.get(ecn.getChangeType()));
187                      DN previousDN = ecn.getPreviousDN();
188                      if (previousDN != null)
189                      {
190                        out.println(INFO_LDAPSEARCH_PSEARCH_PREVIOUS_DN.get(previousDN));
191                      }
192                    } catch (Exception e) {}
193                  }
194                  else if (c.getOID().equals(OID_ACCOUNT_USABLE_CONTROL))
195                  {
196                    try
197                    {
198                      AccountUsableResponseControl acrc =
199                        AccountUsableResponseControl.DECODER
200                        .decode(c.isCritical(), ((LDAPControl) c).getValue());
201
202                      out.println(INFO_LDAPSEARCH_ACCTUSABLE_HEADER.get());
203                      if (acrc.isUsable())
204                      {
205                        out.println(INFO_LDAPSEARCH_ACCTUSABLE_IS_USABLE.get());
206                        if (acrc.getSecondsBeforeExpiration() > 0)
207                        {
208                          int timeToExp = acrc.getSecondsBeforeExpiration();
209                          LocalizableMessage timeToExpStr = secondsToTimeString(timeToExp);
210
211                          out.println(
212                               INFO_LDAPSEARCH_ACCTUSABLE_TIME_UNTIL_EXPIRATION.
213                                       get(timeToExpStr));
214                        }
215                      }
216                      else
217                      {
218                        out.println(
219                                INFO_LDAPSEARCH_ACCTUSABLE_NOT_USABLE.get());
220                        if (acrc.isInactive())
221                        {
222                          out.println(
223                               INFO_LDAPSEARCH_ACCTUSABLE_ACCT_INACTIVE.get());
224                        }
225                        if (acrc.isReset())
226                        {
227                          out.println(
228                                  INFO_LDAPSEARCH_ACCTUSABLE_PW_RESET.get());
229                        }
230                        if (acrc.isExpired())
231                        {
232                          out.println(
233                                  INFO_LDAPSEARCH_ACCTUSABLE_PW_EXPIRED.get());
234
235                          if (acrc.getRemainingGraceLogins() > 0)
236                          {
237                            out.println(
238                                    INFO_LDAPSEARCH_ACCTUSABLE_REMAINING_GRACE
239                                         .get(acrc.getRemainingGraceLogins()));
240                          }
241                        }
242                        if (acrc.isLocked())
243                        {
244                          out.println(INFO_LDAPSEARCH_ACCTUSABLE_LOCKED.get());
245                          if (acrc.getSecondsBeforeUnlock() > 0)
246                          {
247                            int timeToUnlock = acrc.getSecondsBeforeUnlock();
248                            LocalizableMessage timeToUnlockStr =
249                                        secondsToTimeString(timeToUnlock);
250
251                            out.println(
252                                    INFO_LDAPSEARCH_ACCTUSABLE_TIME_UNTIL_UNLOCK
253                                            .get(timeToUnlockStr));
254                          }
255                        }
256                      }
257                    } catch (Exception e) {}
258                  }
259                  else if (c.getOID().equals(OID_ECL_COOKIE_EXCHANGE_CONTROL))
260                  {
261                    try
262                    {
263                      EntryChangelogNotificationControl ctrl =
264                        EntryChangelogNotificationControl.DECODER.decode(
265                          c.isCritical(), ((LDAPControl) c).getValue());
266                      out.println(
267                          INFO_LDAPSEARCH_PUBLIC_CHANGELOG_COOKIE_EXC.get(
268                            c.getOID(), ctrl.getCookie()));
269                    }
270                    catch (Exception e)
271                    {
272                      logger.traceException(e);
273                    }
274                  }
275                }
276
277                SearchResultEntryProtocolOp searchEntryOp =
278                     responseMessage.getSearchResultEntryProtocolOp();
279                StringBuilder sb = new StringBuilder();
280                toLDIF(searchEntryOp, sb, wrapColumn, typesOnly);
281                out.print(sb.toString());
282                matchingEntries++;
283                break;
284
285              case OP_TYPE_SEARCH_RESULT_REFERENCE:
286                SearchResultReferenceProtocolOp searchRefOp =
287                     responseMessage.getSearchResultReferenceProtocolOp();
288                out.println(searchRefOp.toString());
289                break;
290
291              case OP_TYPE_SEARCH_RESULT_DONE:
292                SearchResultDoneProtocolOp searchOp =
293                     responseMessage.getSearchResultDoneProtocolOp();
294                resultCode = searchOp.getResultCode();
295                errorMessage = searchOp.getErrorMessage();
296                matchedDN = searchOp.getMatchedDN();
297
298                for (Control c : responseMessage.getControls())
299                {
300                  if (c.getOID().equals(OID_SERVER_SIDE_SORT_RESPONSE_CONTROL))
301                  {
302                    try
303                    {
304                      ServerSideSortResponseControl sortResponse =
305                        ServerSideSortResponseControl.DECODER
306                        .decode(c.isCritical(), ((LDAPControl) c).getValue());
307                      int rc = sortResponse.getResultCode();
308                      if (rc != LDAPResultCode.SUCCESS)
309                      {
310                        LocalizableMessage msg   = WARN_LDAPSEARCH_SORT_ERROR.get(
311                                LDAPResultCode.toString(rc));
312                        err.println(msg);
313                      }
314                    }
315                    catch (Exception e)
316                    {
317                      LocalizableMessage msg   =
318                              WARN_LDAPSEARCH_CANNOT_DECODE_SORT_RESPONSE.get(
319                                      getExceptionMessage(e));
320                      err.println(msg);
321                    }
322                  }
323                  else if (c.getOID().equals(OID_VLV_RESPONSE_CONTROL))
324                  {
325                    try
326                    {
327                      VLVResponseControl vlvResponse =
328                          VLVResponseControl.DECODER.decode(c.isCritical(),
329                              ((LDAPControl) c).getValue());
330                      int rc = vlvResponse.getVLVResultCode();
331                      if (rc == LDAPResultCode.SUCCESS)
332                      {
333                        LocalizableMessage msg = INFO_LDAPSEARCH_VLV_TARGET_OFFSET.get(
334                                vlvResponse.getTargetPosition());
335                        out.println(msg);
336
337
338                        msg = INFO_LDAPSEARCH_VLV_CONTENT_COUNT.get(
339                                vlvResponse.getContentCount());
340                        out.println(msg);
341                      }
342                      else
343                      {
344                        LocalizableMessage msg = WARN_LDAPSEARCH_VLV_ERROR.get(
345                                LDAPResultCode.toString(rc));
346                        err.println(msg);
347                      }
348                    }
349                    catch (Exception e)
350                    {
351                      LocalizableMessage msg   =
352                              WARN_LDAPSEARCH_CANNOT_DECODE_VLV_RESPONSE.get(
353                                      getExceptionMessage(e));
354                      err.println(msg);
355                    }
356                  }
357                }
358
359                break;
360              default:
361                if(opType == OP_TYPE_EXTENDED_RESPONSE)
362                {
363                  ExtendedResponseProtocolOp op =
364                    responseMessage.getExtendedResponseProtocolOp();
365                  if(op.getOID().equals(OID_NOTICE_OF_DISCONNECTION))
366                  {
367                    resultCode = op.getResultCode();
368                    errorMessage = op.getErrorMessage();
369                    matchedDN = op.getMatchedDN();
370                    break;
371                  }
372                }
373                // FIXME - throw exception?
374              printWrappedText(err, INFO_SEARCH_OPERATION_INVALID_PROTOCOL.get(opType));
375            }
376
377            if(resultCode != SUCCESS)
378            {
379              LocalizableMessage msg = INFO_OPERATION_FAILED.get("SEARCH");
380              throw new LDAPException(resultCode, errorMessage, msg,
381                                      matchedDN, null);
382            }
383            else if (errorMessage != null)
384            {
385              out.println();
386              printWrappedText(out, errorMessage);
387            }
388
389          } while(opType != OP_TYPE_SEARCH_RESULT_DONE);
390
391        } catch(DecodeException ae)
392        {
393          logger.traceException(ae);
394          throw new IOException(ae.getMessage());
395        }
396      }
397    }
398
399    if (searchOptions.countMatchingEntries())
400    {
401      LocalizableMessage message =
402              INFO_LDAPSEARCH_MATCHING_ENTRY_COUNT.get(matchingEntries);
403      out.println(message);
404      out.println();
405    }
406    return matchingEntries;
407  }
408
409  /**
410   * Appends an LDIF representation of the entry to the provided buffer.
411   *
412   * @param  entry       The entry to be written as LDIF.
413   * @param  buffer      The buffer to which the entry should be appended.
414   * @param  wrapColumn  The column at which long lines should be wrapped.
415   * @param  typesOnly   Indicates whether to include only attribute types
416   *                     without values.
417   */
418  public void toLDIF(SearchResultEntryProtocolOp entry, StringBuilder buffer,
419                     int wrapColumn, boolean typesOnly)
420  {
421    // Add the DN to the buffer.
422    String dnString = entry.getDN().toString();
423    int    colsRemaining;
424    if (needsBase64Encoding(dnString))
425    {
426      dnString = Base64.encode(getBytes(dnString));
427      buffer.append("dn:: ");
428
429      colsRemaining = wrapColumn - 5;
430    }
431    else
432    {
433      buffer.append("dn: ");
434
435      colsRemaining = wrapColumn - 4;
436    }
437
438    int dnLength = dnString.length();
439    if (dnLength <= colsRemaining || colsRemaining <= 0)
440    {
441      buffer.append(dnString);
442      buffer.append(EOL);
443    }
444    else
445    {
446      buffer.append(dnString, 0, colsRemaining);
447      buffer.append(EOL);
448
449      int startPos = colsRemaining;
450      while (dnLength - startPos > wrapColumn - 1)
451      {
452        buffer.append(" ");
453        buffer.append(dnString, startPos, startPos+wrapColumn-1);
454        buffer.append(EOL);
455
456        startPos += wrapColumn-1;
457      }
458
459      if (startPos < dnLength)
460      {
461        buffer.append(" ");
462        buffer.append(dnString.substring(startPos));
463        buffer.append(EOL);
464      }
465    }
466
467
468    LinkedList<LDAPAttribute> attributes = entry.getAttributes();
469    // Add the attributes to the buffer.
470    for (LDAPAttribute a : attributes)
471    {
472      String name       = a.getAttributeType();
473      int    nameLength = name.length();
474
475
476      if(typesOnly)
477      {
478          buffer.append(name);
479          buffer.append(EOL);
480      } else
481      {
482        for (ByteString v : a.getValues())
483        {
484          String valueString;
485          if (needsBase64Encoding(v))
486          {
487            valueString = Base64.encode(v);
488            buffer.append(name);
489            buffer.append(":: ");
490
491            colsRemaining = wrapColumn - nameLength - 3;
492          } else
493          {
494            valueString = v.toString();
495            buffer.append(name);
496            buffer.append(": ");
497
498            colsRemaining = wrapColumn - nameLength - 2;
499          }
500
501          int valueLength = valueString.length();
502          if (valueLength <= colsRemaining || colsRemaining <= 0)
503          {
504            buffer.append(valueString);
505            buffer.append(EOL);
506          } else
507          {
508            buffer.append(valueString, 0, colsRemaining);
509            buffer.append(EOL);
510
511            int startPos = colsRemaining;
512            while (valueLength - startPos > wrapColumn - 1)
513            {
514              buffer.append(" ");
515              buffer.append(valueString, startPos, startPos+wrapColumn-1);
516              buffer.append(EOL);
517
518              startPos += wrapColumn-1;
519            }
520
521            if (startPos < valueLength)
522            {
523              buffer.append(" ");
524              buffer.append(valueString.substring(startPos));
525              buffer.append(EOL);
526            }
527          }
528        }
529      }
530    }
531
532
533    // Make sure to add an extra blank line to ensure that there will be one
534    // between this entry and the next.
535    buffer.append(EOL);
536  }
537
538  /**
539   * Retrieves the set of response controls included in the last search result
540   * done message.
541   *
542   * @return  The set of response controls included in the last search result
543   *          done message.
544   */
545  public List<Control> getResponseControls()
546  {
547    return responseControls;
548  }
549
550  /**
551   * The main method for LDAPSearch tool.
552   *
553   * @param  args  The command-line arguments provided to this program.
554   */
555
556  public static void main(String[] args)
557  {
558    int retCode = mainSearch(args, true, false, System.out, System.err);
559
560    if(retCode != 0)
561    {
562      System.exit(filterExitCode(retCode));
563    }
564  }
565
566  /**
567   * Parses the provided command-line arguments and uses that information to
568   * run the ldapsearch tool.
569   *
570   * @param  args  The command-line arguments provided to this program.
571   *
572   * @return The error code.
573   */
574
575  public static int mainSearch(String[] args)
576  {
577    return mainSearch(args, true, true, System.out, System.err);
578  }
579
580  /**
581   * Parses the provided command-line arguments and uses that information to
582   * run the ldapsearch tool.
583   *
584   * @param  args              The command-line arguments provided to this
585   *                           program.
586   * @param  initializeServer  Indicates whether to initialize the server.
587   * @param  outStream         The output stream to use for standard output, or
588   *                           <CODE>null</CODE> if standard output is not
589   *                           needed.
590   * @param  errStream         The output stream to use for standard error, or
591   *                           <CODE>null</CODE> if standard error is not
592   *                           needed.
593   *
594   * @return The error code.
595   */
596  public static int mainSearch(String[] args, boolean initializeServer,
597                               OutputStream outStream, OutputStream errStream)
598  {
599    return mainSearch(args, initializeServer, true, outStream, errStream);
600  }
601
602  /**
603   * Parses the provided command-line arguments and uses that information to
604   * run the ldapsearch tool.
605   *
606   * @param  args              The command-line arguments provided to this
607   *                           program.
608   * @param  initializeServer  Indicates whether to initialize the server.
609   * @param  returnMatchingEntries whether when the option --countEntries is
610   *                           specified, the number of matching entries should
611   *                           be returned or not.
612   * @param  outStream         The output stream to use for standard output, or
613   *                           <CODE>null</CODE> if standard output is not
614   *                           needed.
615   * @param  errStream         The output stream to use for standard error, or
616   *                           <CODE>null</CODE> if standard error is not
617   *                           needed.
618   *
619   * @return The error code.
620   */
621
622  public static int mainSearch(String[] args, boolean initializeServer,
623      boolean returnMatchingEntries, OutputStream outStream,
624      OutputStream errStream)
625  {
626    PrintStream out = NullOutputStream.wrapOrNullStream(outStream);
627    PrintStream err = NullOutputStream.wrapOrNullStream(errStream);
628
629    LDAPConnectionOptions connectionOptions = new LDAPConnectionOptions();
630    LDAPSearchOptions searchOptions = new LDAPSearchOptions();
631    LDAPConnection connection = null;
632    ArrayList<LDAPFilter> filters = new ArrayList<>();
633    LinkedHashSet<String> attributes = new LinkedHashSet<>();
634
635    BooleanArgument   continueOnError          = null;
636    BooleanArgument   countEntries             = null;
637    BooleanArgument   dontWrap                 = null;
638    BooleanArgument   noop                     = null;
639    BooleanArgument   reportAuthzID            = null;
640    BooleanArgument   saslExternal             = null;
641    BooleanArgument   showUsage                = null;
642    BooleanArgument   trustAll                 = null;
643    BooleanArgument   usePasswordPolicyControl = null;
644    BooleanArgument   useSSL                   = null;
645    BooleanArgument   startTLS                 = null;
646    BooleanArgument   typesOnly                = null;
647    BooleanArgument   verbose                  = null;
648    FileBasedArgument bindPasswordFile         = null;
649    FileBasedArgument keyStorePasswordFile     = null;
650    FileBasedArgument trustStorePasswordFile   = null;
651    IntegerArgument   port                     = null;
652    IntegerArgument   simplePageSize           = null;
653    IntegerArgument   sizeLimit                = null;
654    IntegerArgument   timeLimit                = null;
655    IntegerArgument   version                  = null;
656    StringArgument    assertionFilter          = null;
657    StringArgument    baseDN                   = null;
658    StringArgument    bindDN                   = null;
659    StringArgument    bindPassword             = null;
660    StringArgument    certNickname             = null;
661    StringArgument    controlStr               = null;
662    StringArgument    dereferencePolicy        = null;
663    StringArgument    encodingStr              = null;
664    StringArgument    filename                 = null;
665    StringArgument    hostName                 = null;
666    StringArgument    keyStorePath             = null;
667    StringArgument    keyStorePassword         = null;
668    StringArgument    matchedValuesFilter      = null;
669    StringArgument    proxyAuthzID             = null;
670    StringArgument    pSearchInfo              = null;
671    StringArgument    saslOptions              = null;
672    MultiChoiceArgument searchScope              = null;
673    StringArgument    sortOrder                = null;
674    StringArgument    trustStorePath           = null;
675    StringArgument    trustStorePassword       = null;
676    IntegerArgument   connectTimeout           = null;
677    StringArgument    vlvDescriptor            = null;
678    StringArgument    effectiveRightsUser      = null;
679    StringArgument    effectiveRightsAttrs     = null;
680    StringArgument    propertiesFileArgument   = null;
681    BooleanArgument   noPropertiesFileArgument = null;
682    BooleanArgument   subEntriesArgument       = null;
683
684
685    // Create the command-line argument parser for use with this program.
686    LocalizableMessage toolDescription = INFO_LDAPSEARCH_TOOL_DESCRIPTION.get();
687    ArgumentParser argParser = new ArgumentParser(CLASS_NAME, toolDescription,
688                                                  false, true, 0, 0,
689                                                  "[filter] [attributes ...]");
690    argParser.setShortToolDescription(REF_SHORT_DESC_LDAPSEARCH.get());
691    argParser.setVersionHandler(new DirectoryServerVersionHandler());
692
693    try
694    {
695      propertiesFileArgument = new StringArgument("propertiesFilePath",
696          null, OPTION_LONG_PROP_FILE_PATH,
697          false, false, true, INFO_PROP_FILE_PATH_PLACEHOLDER.get(), null, null,
698          INFO_DESCRIPTION_PROP_FILE_PATH.get());
699      argParser.addArgument(propertiesFileArgument);
700      argParser.setFilePropertiesArgument(propertiesFileArgument);
701
702      noPropertiesFileArgument = new BooleanArgument(
703          "noPropertiesFileArgument", null, OPTION_LONG_NO_PROP_FILE,
704          INFO_DESCRIPTION_NO_PROP_FILE.get());
705      argParser.addArgument(noPropertiesFileArgument);
706      argParser.setNoPropertiesFileArgument(noPropertiesFileArgument);
707
708      hostName = new StringArgument("host", OPTION_SHORT_HOST,
709                                    OPTION_LONG_HOST, false, false, true,
710                                    INFO_HOST_PLACEHOLDER.get(), "localhost",
711                                    null,
712                                    INFO_DESCRIPTION_HOST.get());
713      hostName.setPropertyName(OPTION_LONG_HOST);
714      argParser.addArgument(hostName);
715
716      port = new IntegerArgument("port", OPTION_SHORT_PORT,
717                                 OPTION_LONG_PORT, false, false, true,
718                                 INFO_PORT_PLACEHOLDER.get(), 389, null,
719                                 true, 1, true, 65535,
720                                 INFO_DESCRIPTION_PORT.get());
721      port.setPropertyName(OPTION_LONG_PORT);
722      argParser.addArgument(port);
723
724      useSSL = new BooleanArgument("useSSL", OPTION_SHORT_USE_SSL,
725                                   OPTION_LONG_USE_SSL,
726                                   INFO_DESCRIPTION_USE_SSL.get());
727      useSSL.setPropertyName(OPTION_LONG_USE_SSL);
728      argParser.addArgument(useSSL);
729
730      startTLS = new BooleanArgument("startTLS", OPTION_SHORT_START_TLS,
731                                    OPTION_LONG_START_TLS,
732                                    INFO_DESCRIPTION_START_TLS.get());
733      startTLS.setPropertyName(OPTION_LONG_START_TLS);
734      argParser.addArgument(startTLS);
735
736      bindDN = new StringArgument("bindDN", OPTION_SHORT_BINDDN,
737                                  OPTION_LONG_BINDDN, false, false, true,
738                                  INFO_BINDDN_PLACEHOLDER.get(), null, null,
739                                  INFO_DESCRIPTION_BINDDN.get());
740      bindDN.setPropertyName(OPTION_LONG_BINDDN);
741      argParser.addArgument(bindDN);
742
743      bindPassword = new StringArgument("bindPassword", OPTION_SHORT_BINDPWD,
744                                        OPTION_LONG_BINDPWD,
745                                        false, false, true,
746                                        INFO_BINDPWD_PLACEHOLDER.get(),
747                                        null, null,
748                                        INFO_DESCRIPTION_BINDPASSWORD.get());
749      bindPassword.setPropertyName(OPTION_LONG_BINDPWD);
750      argParser.addArgument(bindPassword);
751
752      bindPasswordFile =
753           new FileBasedArgument("bindPasswordFile", OPTION_SHORT_BINDPWD_FILE,
754                                 OPTION_LONG_BINDPWD_FILE,
755                                 false, false,
756                                 INFO_BINDPWD_FILE_PLACEHOLDER.get(), null,
757                                 null, INFO_DESCRIPTION_BINDPASSWORDFILE.get());
758      bindPasswordFile.setPropertyName(OPTION_LONG_BINDPWD_FILE);
759      argParser.addArgument(bindPasswordFile);
760
761      baseDN = new StringArgument("baseDN", OPTION_SHORT_BASEDN,
762                                  OPTION_LONG_BASEDN, true, false, true,
763                                  INFO_BASEDN_PLACEHOLDER.get(), null, null,
764                                  INFO_SEARCH_DESCRIPTION_BASEDN.get());
765      baseDN.setPropertyName(OPTION_LONG_BASEDN);
766      argParser.addArgument(baseDN);
767
768      HashSet<String> allowedScopes = new HashSet<>();
769      allowedScopes.add("base");
770      allowedScopes.add("one");
771      allowedScopes.add("sub");
772      allowedScopes.add("subordinate");
773      searchScope = new MultiChoiceArgument(
774              "searchScope", 's', "searchScope", false,
775              true, INFO_SEARCH_SCOPE_PLACEHOLDER.get(), allowedScopes,
776              false,
777              INFO_SEARCH_DESCRIPTION_SEARCH_SCOPE.get());
778      searchScope.setPropertyName("searchScope");
779      searchScope.setDefaultValue("sub");
780      argParser.addArgument(searchScope);
781
782      filename = new StringArgument("filename", OPTION_SHORT_FILENAME,
783                                    OPTION_LONG_FILENAME, false, false,
784                                    true, INFO_FILE_PLACEHOLDER.get(), null,
785                                    null,
786                                    INFO_SEARCH_DESCRIPTION_FILENAME.get());
787      searchScope.setPropertyName(OPTION_LONG_FILENAME);
788      argParser.addArgument(filename);
789
790      saslExternal = new BooleanArgument(
791              "useSASLExternal", 'r',
792              "useSASLExternal",
793              INFO_DESCRIPTION_USE_SASL_EXTERNAL.get());
794      saslExternal.setPropertyName("useSASLExternal");
795      argParser.addArgument(saslExternal);
796
797      saslOptions = new StringArgument("saslOption", OPTION_SHORT_SASLOPTION,
798                                       OPTION_LONG_SASLOPTION, false,
799                                       true, true,
800                                       INFO_SASL_OPTION_PLACEHOLDER.get(), null,
801                                       null,
802                                       INFO_DESCRIPTION_SASL_PROPERTIES.get());
803      saslOptions.setPropertyName(OPTION_LONG_SASLOPTION);
804      argParser.addArgument(saslOptions);
805
806      trustAll = CommonArguments.getTrustAll();
807      argParser.addArgument(trustAll);
808
809      keyStorePath = new StringArgument("keyStorePath",
810                                  OPTION_SHORT_KEYSTOREPATH,
811                                  OPTION_LONG_KEYSTOREPATH, false, false, true,
812                                  INFO_KEYSTOREPATH_PLACEHOLDER.get(), null,
813                                  null,
814                                  INFO_DESCRIPTION_KEYSTOREPATH.get());
815      keyStorePath.setPropertyName(OPTION_LONG_KEYSTOREPATH);
816      argParser.addArgument(keyStorePath);
817
818      keyStorePassword = new StringArgument("keyStorePassword",
819                                  OPTION_SHORT_KEYSTORE_PWD,
820                                  OPTION_LONG_KEYSTORE_PWD, false, false,
821                                  true, INFO_KEYSTORE_PWD_PLACEHOLDER.get(),
822                                  null, null,
823                                  INFO_DESCRIPTION_KEYSTOREPASSWORD.get());
824      keyStorePassword.setPropertyName(OPTION_LONG_KEYSTORE_PWD);
825      argParser.addArgument(keyStorePassword);
826
827      keyStorePasswordFile =
828           new FileBasedArgument("keystorepasswordfile",
829                                 OPTION_SHORT_KEYSTORE_PWD_FILE,
830                                 OPTION_LONG_KEYSTORE_PWD_FILE,
831                                 false, false,
832                                 INFO_KEYSTORE_PWD_FILE_PLACEHOLDER.get(),
833                                 null, null,
834                                 INFO_DESCRIPTION_KEYSTOREPASSWORD_FILE.get());
835      keyStorePasswordFile.setPropertyName(OPTION_LONG_KEYSTORE_PWD_FILE);
836      argParser.addArgument(keyStorePasswordFile);
837
838      certNickname = new StringArgument(
839              "certnickname", OPTION_SHORT_CERT_NICKNAME,
840              OPTION_LONG_CERT_NICKNAME,
841              false, false, true, INFO_NICKNAME_PLACEHOLDER.get(), null,
842              null, INFO_DESCRIPTION_CERT_NICKNAME.get());
843      certNickname.setPropertyName(OPTION_LONG_CERT_NICKNAME);
844      argParser.addArgument(certNickname);
845
846      trustStorePath = new StringArgument("trustStorePath",
847                                  OPTION_SHORT_TRUSTSTOREPATH,
848                                  OPTION_LONG_TRUSTSTOREPATH,
849                                  false, false, true,
850                                  INFO_TRUSTSTOREPATH_PLACEHOLDER.get(), null,
851                                  null,
852                                  INFO_DESCRIPTION_TRUSTSTOREPATH.get());
853      trustStorePath.setPropertyName(OPTION_LONG_TRUSTSTOREPATH);
854      argParser.addArgument(trustStorePath);
855
856      trustStorePassword =
857           new StringArgument("trustStorePassword", null,
858                              OPTION_LONG_TRUSTSTORE_PWD,
859                              false, false, true,
860                              INFO_TRUSTSTORE_PWD_PLACEHOLDER.get(),
861                              null,
862                              null, INFO_DESCRIPTION_TRUSTSTOREPASSWORD.get());
863      trustStorePassword.setPropertyName(OPTION_LONG_TRUSTSTORE_PWD);
864      argParser.addArgument(trustStorePassword);
865
866      trustStorePasswordFile =
867           new FileBasedArgument(
868                   "truststorepasswordfile",
869                   OPTION_SHORT_TRUSTSTORE_PWD_FILE,
870                   OPTION_LONG_TRUSTSTORE_PWD_FILE, false, false,
871                   INFO_TRUSTSTORE_PWD_FILE_PLACEHOLDER.get(), null, null,
872                   INFO_DESCRIPTION_TRUSTSTOREPASSWORD_FILE.get());
873      trustStorePasswordFile.setPropertyName(OPTION_LONG_TRUSTSTORE_PWD_FILE);
874      argParser.addArgument(trustStorePasswordFile);
875
876      proxyAuthzID = new StringArgument("proxy_authzid",
877                                        OPTION_SHORT_PROXYAUTHID,
878                                        OPTION_LONG_PROXYAUTHID, false,
879                                        false, true,
880                                        INFO_PROXYAUTHID_PLACEHOLDER.get(),
881                                        null, null,
882                                        INFO_DESCRIPTION_PROXY_AUTHZID.get());
883      proxyAuthzID.setPropertyName(OPTION_LONG_PROXYAUTHID);
884      argParser.addArgument(proxyAuthzID);
885
886      reportAuthzID = new BooleanArgument(
887              "reportauthzid", 'E', OPTION_LONG_REPORT_AUTHZ_ID,
888              INFO_DESCRIPTION_REPORT_AUTHZID.get());
889      reportAuthzID.setPropertyName(OPTION_LONG_REPORT_AUTHZ_ID);
890      argParser.addArgument(reportAuthzID);
891
892      usePasswordPolicyControl = new BooleanArgument(
893              "usepwpolicycontrol", null,
894              OPTION_LONG_USE_PW_POLICY_CTL,
895              INFO_DESCRIPTION_USE_PWP_CONTROL.get());
896      usePasswordPolicyControl.setPropertyName(OPTION_LONG_USE_PW_POLICY_CTL);
897      argParser.addArgument(usePasswordPolicyControl);
898
899      pSearchInfo = new StringArgument("psearchinfo", 'C', "persistentSearch",
900                             false, false, true,
901                             INFO_PSEARCH_PLACEHOLDER.get(),
902                              null, null, INFO_DESCRIPTION_PSEARCH_INFO.get());
903      pSearchInfo.setPropertyName("persistentSearch");
904      pSearchInfo.setDocDescriptionSupplement(SUPPLEMENT_DESCRIPTION_PSEARCH_INFO.get());
905      argParser.addArgument(pSearchInfo);
906
907      simplePageSize = new IntegerArgument(
908              "simplepagesize", null,
909              "simplePageSize", false, false, true,
910              INFO_NUM_ENTRIES_PLACEHOLDER.get(), 1000, null, true, 1,
911              false, 0,
912              INFO_DESCRIPTION_SIMPLE_PAGE_SIZE.get());
913      simplePageSize.setPropertyName("simplePageSize");
914      argParser.addArgument(simplePageSize);
915
916      assertionFilter = new StringArgument(
917              "assertionfilter", null,
918              OPTION_LONG_ASSERTION_FILE,
919              false, false,
920              true, INFO_ASSERTION_FILTER_PLACEHOLDER.get(),
921              null, null,
922              INFO_DESCRIPTION_ASSERTION_FILTER.get());
923      assertionFilter.setPropertyName(OPTION_LONG_ASSERTION_FILE);
924      argParser.addArgument(assertionFilter);
925
926      matchedValuesFilter = new StringArgument(
927              "matchedvalues", null,
928              "matchedValuesFilter", false, true, true,
929              INFO_FILTER_PLACEHOLDER.get(), null, null,
930              INFO_DESCRIPTION_MATCHED_VALUES_FILTER.get());
931      matchedValuesFilter.setPropertyName("matchedValuesFilter");
932      argParser.addArgument(matchedValuesFilter);
933
934      sortOrder = new StringArgument(
935              "sortorder", 'S', "sortOrder", false,
936              false, true, INFO_SORT_ORDER_PLACEHOLDER.get(), null, null,
937              INFO_DESCRIPTION_SORT_ORDER.get());
938      sortOrder.setPropertyName("sortOrder");
939      argParser.addArgument(sortOrder);
940
941      vlvDescriptor =
942           new StringArgument(
943                   "vlvdescriptor", 'G', "virtualListView", false,
944                   false, true,
945                   INFO_VLV_PLACEHOLDER.get(),
946                   null, null, INFO_DESCRIPTION_VLV.get());
947      vlvDescriptor.setPropertyName("virtualListView");
948      argParser.addArgument(vlvDescriptor);
949
950      controlStr =
951           new StringArgument("control", 'J', "control", false, true, true,
952                    INFO_LDAP_CONTROL_PLACEHOLDER.get(),
953                    null, null, INFO_DESCRIPTION_CONTROLS.get());
954      controlStr.setPropertyName("control");
955      controlStr.setDocDescriptionSupplement(SUPPLEMENT_DESCRIPTION_CONTROLS.get());
956      argParser.addArgument(controlStr);
957
958      subEntriesArgument = new BooleanArgument("subEntries",
959              OPTION_SHORT_SUBENTRIES, OPTION_LONG_SUBENTRIES,
960              INFO_DESCRIPTION_SUBENTRIES.get());
961      subEntriesArgument.setPropertyName(OPTION_LONG_SUBENTRIES);
962      argParser.addArgument(subEntriesArgument);
963
964      effectiveRightsUser =
965              new StringArgument("effectiveRightsUser",
966                      OPTION_SHORT_EFFECTIVERIGHTSUSER,
967                      OPTION_LONG_EFFECTIVERIGHTSUSER, false, false, true,
968                      INFO_PROXYAUTHID_PLACEHOLDER.get(), null, null,
969                      INFO_DESCRIPTION_EFFECTIVERIGHTS_USER.get( ));
970      effectiveRightsUser.setPropertyName(OPTION_LONG_EFFECTIVERIGHTSUSER);
971      argParser.addArgument(effectiveRightsUser);
972
973      effectiveRightsAttrs =
974              new StringArgument("effectiveRightsAttrs",
975                      OPTION_SHORT_EFFECTIVERIGHTSATTR,
976                      OPTION_LONG_EFFECTIVERIGHTSATTR, false, true, true,
977                      INFO_ATTRIBUTE_PLACEHOLDER.get(), null, null,
978                      INFO_DESCRIPTION_EFFECTIVERIGHTS_ATTR.get( ));
979      effectiveRightsAttrs.setPropertyName(OPTION_LONG_EFFECTIVERIGHTSATTR);
980      argParser.addArgument(effectiveRightsAttrs);
981
982      version = new IntegerArgument("version", OPTION_SHORT_PROTOCOL_VERSION,
983                                    OPTION_LONG_PROTOCOL_VERSION, false, false,
984                                    true,
985                                    INFO_PROTOCOL_VERSION_PLACEHOLDER.get(), 3,
986                                    null, INFO_DESCRIPTION_VERSION.get());
987      version.setPropertyName(OPTION_LONG_PROTOCOL_VERSION);
988      argParser.addArgument(version);
989
990      int defaultTimeout = CliConstants.DEFAULT_LDAP_CONNECT_TIMEOUT;
991      connectTimeout = new IntegerArgument(OPTION_LONG_CONNECT_TIMEOUT,
992          null, OPTION_LONG_CONNECT_TIMEOUT,
993          false, false, true, INFO_TIMEOUT_PLACEHOLDER.get(),
994          defaultTimeout, null,
995          true, 0, false, Integer.MAX_VALUE,
996          INFO_DESCRIPTION_CONNECTION_TIMEOUT.get());
997      connectTimeout.setPropertyName(OPTION_LONG_CONNECT_TIMEOUT);
998      argParser.addArgument(connectTimeout);
999
1000      encodingStr = new StringArgument("encoding", 'i', "encoding", false,
1001                                       false, true,
1002                                       INFO_ENCODING_PLACEHOLDER.get(), null,
1003                                       null,
1004                                       INFO_DESCRIPTION_ENCODING.get());
1005      encodingStr.setPropertyName("encoding");
1006      argParser.addArgument(encodingStr);
1007
1008      dereferencePolicy =
1009           new StringArgument("derefpolicy", 'a', "dereferencePolicy", false,
1010                              false, true,
1011                              INFO_DEREFERENCE_POLICE_PLACEHOLDER.get(), "never",
1012                              null,
1013                              INFO_SEARCH_DESCRIPTION_DEREFERENCE_POLICY.get());
1014      dereferencePolicy.setPropertyName("dereferencePolicy");
1015      argParser.addArgument(dereferencePolicy);
1016
1017      typesOnly = new BooleanArgument("typesOnly", 'A', "typesOnly",
1018                                      INFO_DESCRIPTION_TYPES_ONLY.get());
1019      typesOnly.setPropertyName("typesOnly");
1020      argParser.addArgument(typesOnly);
1021
1022      sizeLimit = new IntegerArgument("sizeLimit", 'z', "sizeLimit", false,
1023                                      false, true,
1024                                      INFO_SIZE_LIMIT_PLACEHOLDER.get(), 0,
1025                                      null,
1026                                      INFO_SEARCH_DESCRIPTION_SIZE_LIMIT.get());
1027      sizeLimit.setPropertyName("sizeLimit");
1028      argParser.addArgument(sizeLimit);
1029
1030      timeLimit = new IntegerArgument("timeLimit", 'l', "timeLimit", false,
1031                                      false, true,
1032                                      INFO_TIME_LIMIT_PLACEHOLDER.get(), 0,
1033                                      null,
1034                                      INFO_SEARCH_DESCRIPTION_TIME_LIMIT.get());
1035      timeLimit.setPropertyName("timeLimit");
1036      argParser.addArgument(timeLimit);
1037
1038      dontWrap = new BooleanArgument("dontwrap", 'T',
1039                                     "dontWrap",
1040                                     INFO_DESCRIPTION_DONT_WRAP.get());
1041      dontWrap.setPropertyName("dontWrap");
1042      argParser.addArgument(dontWrap);
1043
1044      countEntries = new BooleanArgument("countentries", null, "countEntries",
1045                                         INFO_DESCRIPTION_COUNT_ENTRIES.get());
1046      countEntries.setPropertyName("countEntries");
1047      argParser.addArgument(countEntries);
1048
1049      continueOnError =
1050           new BooleanArgument("continueOnError", 'c', "continueOnError",
1051                               INFO_DESCRIPTION_CONTINUE_ON_ERROR.get());
1052      continueOnError.setPropertyName("continueOnError");
1053      argParser.addArgument(continueOnError);
1054
1055      noop = new BooleanArgument("noop", OPTION_SHORT_DRYRUN,
1056          OPTION_LONG_DRYRUN, INFO_DESCRIPTION_NOOP.get());
1057      noop.setPropertyName(OPTION_LONG_DRYRUN);
1058      argParser.addArgument(noop);
1059
1060      verbose = CommonArguments.getVerbose();
1061      argParser.addArgument(verbose);
1062
1063      showUsage = CommonArguments.getShowUsage();
1064      argParser.addArgument(showUsage);
1065      argParser.setUsageArgument(showUsage, out);
1066    } catch (ArgumentException ae)
1067    {
1068      printWrappedText(err, ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage()));
1069      return CLIENT_SIDE_PARAM_ERROR;
1070    }
1071
1072    // Parse the command-line arguments provided to this program.
1073    try
1074    {
1075      argParser.parseArguments(args);
1076    }
1077    catch (ArgumentException ae)
1078    {
1079      argParser.displayMessageAndUsageReference(err, ERR_ERROR_PARSING_ARGS.get(ae.getMessage()));
1080      return CLIENT_SIDE_PARAM_ERROR;
1081    }
1082
1083    // If we should just display usage or version information,
1084    // then print it and exit.
1085    if (argParser.usageOrVersionDisplayed())
1086    {
1087      return 0;
1088    }
1089
1090    ArrayList<String> filterAndAttributeStrings =
1091      argParser.getTrailingArguments();
1092    if(!filterAndAttributeStrings.isEmpty())
1093    {
1094      // the list of trailing arguments should be structured as follow:
1095      // - If a filter file is present, trailing arguments are considered
1096      //   as attributes
1097      // - If filter file is not present, the first trailing argument is
1098      // considered the filter, the other as attributes.
1099      if (! filename.isPresent())
1100      {
1101        String filterString = filterAndAttributeStrings.remove(0);
1102
1103        try
1104        {
1105          filters.add(LDAPFilter.decode(filterString));
1106        } catch (LDAPException le)
1107        {
1108          logger.traceException(le);
1109          printWrappedText(err, le.getMessage());
1110          return CLIENT_SIDE_PARAM_ERROR;
1111        }
1112      }
1113      attributes.addAll(filterAndAttributeStrings);
1114    }
1115
1116    if(bindPassword.isPresent() && bindPasswordFile.isPresent())
1117    {
1118      printWrappedText(err,
1119          ERR_TOOL_CONFLICTING_ARGS.get(bindPassword.getLongIdentifier(), bindPasswordFile.getLongIdentifier()));
1120      return CLIENT_SIDE_PARAM_ERROR;
1121    }
1122
1123    if (useSSL.isPresent() && startTLS.isPresent())
1124    {
1125      printWrappedText(err, ERR_TOOL_CONFLICTING_ARGS.get(useSSL.getLongIdentifier(), startTLS.getLongIdentifier()));
1126      return CLIENT_SIDE_PARAM_ERROR;
1127    }
1128
1129    if (keyStorePassword.isPresent() && keyStorePasswordFile.isPresent())
1130    {
1131      printWrappedText(err, ERR_TOOL_CONFLICTING_ARGS.get(
1132          keyStorePassword.getLongIdentifier(), keyStorePasswordFile.getLongIdentifier()));
1133      return CLIENT_SIDE_PARAM_ERROR;
1134    }
1135
1136    if (trustStorePassword.isPresent() && trustStorePasswordFile.isPresent())
1137    {
1138      printWrappedText(err, ERR_TOOL_CONFLICTING_ARGS.get(
1139          trustStorePassword.getLongIdentifier(), trustStorePasswordFile.getLongIdentifier()));
1140      return CLIENT_SIDE_PARAM_ERROR;
1141    }
1142
1143    String hostNameValue = hostName.getValue();
1144    int portNumber = 389;
1145    try
1146    {
1147      portNumber = port.getIntValue();
1148    } catch(ArgumentException ae)
1149    {
1150      logger.traceException(ae);
1151      argParser.displayMessageAndUsageReference(err, ae.getMessageObject());
1152      return CLIENT_SIDE_PARAM_ERROR;
1153    }
1154
1155    // Read the LDAP version number.
1156    try
1157    {
1158      int versionNumber = version.getIntValue();
1159      if(versionNumber != 2 && versionNumber != 3)
1160      {
1161        printWrappedText(err, ERR_DESCRIPTION_INVALID_VERSION.get(versionNumber));
1162        return CLIENT_SIDE_PARAM_ERROR;
1163      }
1164      connectionOptions.setVersionNumber(versionNumber);
1165    } catch(ArgumentException ae)
1166    {
1167      logger.traceException(ae);
1168      argParser.displayMessageAndUsageReference(err, ae.getMessageObject());
1169      return CLIENT_SIDE_PARAM_ERROR;
1170    }
1171
1172
1173    // Indicate whether we should report the authorization ID and/or use the
1174    // password policy control.
1175    connectionOptions.setReportAuthzID(reportAuthzID.isPresent());
1176    connectionOptions.setUsePasswordPolicyControl(
1177         usePasswordPolicyControl.isPresent());
1178
1179
1180    String baseDNValue = baseDN.getValue();
1181    String bindDNValue = bindDN.getValue();
1182    String fileNameValue = filename.getValue();
1183    String bindPasswordValue;
1184    try
1185    {
1186      bindPasswordValue = getPasswordValue(
1187          bindPassword, bindPasswordFile, bindDNValue, out, err);
1188    }
1189    catch (Exception ex)
1190    {
1191      logger.traceException(ex);
1192      printWrappedText(err, ex.getMessage());
1193      return CLIENT_SIDE_PARAM_ERROR;
1194    }
1195
1196    String keyStorePathValue = keyStorePath.getValue();
1197    String trustStorePathValue = trustStorePath.getValue();
1198
1199    String keyStorePasswordValue = null;
1200    if (keyStorePassword.isPresent())
1201    {
1202      keyStorePasswordValue = keyStorePassword.getValue();
1203    }
1204    else if (keyStorePasswordFile.isPresent())
1205    {
1206      keyStorePasswordValue = keyStorePasswordFile.getValue();
1207    }
1208
1209    String trustStorePasswordValue = null;
1210    if (trustStorePassword.isPresent())
1211    {
1212      trustStorePasswordValue = trustStorePassword.getValue();
1213    }
1214    else if (trustStorePasswordFile.isPresent())
1215    {
1216      trustStorePasswordValue = trustStorePasswordFile.getValue();
1217    }
1218
1219    searchOptions.setTypesOnly(typesOnly.isPresent());
1220    searchOptions.setShowOperations(noop.isPresent());
1221    searchOptions.setVerbose(verbose.isPresent());
1222    searchOptions.setContinueOnError(continueOnError.isPresent());
1223    searchOptions.setEncoding(encodingStr.getValue());
1224    searchOptions.setCountMatchingEntries(countEntries.isPresent());
1225    try
1226    {
1227      searchOptions.setTimeLimit(timeLimit.getIntValue());
1228      searchOptions.setSizeLimit(sizeLimit.getIntValue());
1229    } catch(ArgumentException ex1)
1230    {
1231      argParser.displayMessageAndUsageReference(err, ex1.getMessageObject());
1232      return CLIENT_SIDE_PARAM_ERROR;
1233    }
1234    if (!searchOptions.setSearchScope(searchScope.getValue(), err)
1235        || !searchOptions.setDereferencePolicy(dereferencePolicy.getValue(), err))
1236    {
1237      return CLIENT_SIDE_PARAM_ERROR;
1238    }
1239
1240    if(controlStr.isPresent())
1241    {
1242      for (String ctrlString : controlStr.getValues())
1243      {
1244        Control ctrl = LDAPToolUtils.getControl(ctrlString, err);
1245        if(ctrl == null)
1246        {
1247          printWrappedText(err, ERR_TOOL_INVALID_CONTROL_STRING.get(ctrlString));
1248          return CLIENT_SIDE_PARAM_ERROR;
1249        }
1250        searchOptions.getControls().add(ctrl);
1251      }
1252    }
1253
1254    if(effectiveRightsUser.isPresent()) {
1255      String authzID=effectiveRightsUser.getValue();
1256      if (!authzID.startsWith("dn:")) {
1257        printWrappedText(err, ERR_EFFECTIVERIGHTS_INVALID_AUTHZID.get(authzID));
1258        return CLIENT_SIDE_PARAM_ERROR;
1259      }
1260      Control effectiveRightsControl =
1261          new GetEffectiveRightsRequestControl(false, authzID.substring(3),
1262              effectiveRightsAttrs.getValues());
1263      searchOptions.getControls().add(effectiveRightsControl);
1264    }
1265
1266    if (proxyAuthzID.isPresent())
1267    {
1268      Control proxyControl =
1269          new ProxiedAuthV2Control(true,
1270              ByteString.valueOfUtf8(proxyAuthzID.getValue()));
1271      searchOptions.getControls().add(proxyControl);
1272    }
1273
1274    if (pSearchInfo.isPresent())
1275    {
1276      String infoString = toLowerCase(pSearchInfo.getValue().trim());
1277      HashSet<PersistentSearchChangeType> changeTypes = new HashSet<>();
1278      boolean changesOnly = true;
1279      boolean returnECs = true;
1280
1281      StringTokenizer tokenizer = new StringTokenizer(infoString, ":");
1282
1283      if (! tokenizer.hasMoreTokens())
1284      {
1285        printWrappedText(err, ERR_PSEARCH_MISSING_DESCRIPTOR.get());
1286        return CLIENT_SIDE_PARAM_ERROR;
1287      }
1288      else
1289      {
1290        String token = tokenizer.nextToken();
1291        if (! token.equals("ps"))
1292        {
1293          printWrappedText(err, ERR_PSEARCH_DOESNT_START_WITH_PS.get(infoString));
1294          return CLIENT_SIDE_PARAM_ERROR;
1295        }
1296      }
1297
1298      if (tokenizer.hasMoreTokens())
1299      {
1300        StringTokenizer st = new StringTokenizer(tokenizer.nextToken(), ", ");
1301        while (st.hasMoreTokens())
1302        {
1303          String token = st.nextToken();
1304          if (token.equals("add"))
1305          {
1306            changeTypes.add(PersistentSearchChangeType.ADD);
1307          }
1308          else if (token.equals("delete") || token.equals("del"))
1309          {
1310            changeTypes.add(PersistentSearchChangeType.DELETE);
1311          }
1312          else if (token.equals("modify") || token.equals("mod"))
1313          {
1314            changeTypes.add(PersistentSearchChangeType.MODIFY);
1315          }
1316          else if (token.equals("modifydn") || token.equals("moddn") ||
1317                   token.equals("modrdn"))
1318          {
1319            changeTypes.add(PersistentSearchChangeType.MODIFY_DN);
1320          }
1321          else if (token.equals("any") || token.equals("all"))
1322          {
1323            changeTypes.add(PersistentSearchChangeType.ADD);
1324            changeTypes.add(PersistentSearchChangeType.DELETE);
1325            changeTypes.add(PersistentSearchChangeType.MODIFY);
1326            changeTypes.add(PersistentSearchChangeType.MODIFY_DN);
1327          }
1328          else
1329          {
1330            printWrappedText(err, ERR_PSEARCH_INVALID_CHANGE_TYPE.get(token));
1331            return CLIENT_SIDE_PARAM_ERROR;
1332          }
1333        }
1334      }
1335
1336      if (changeTypes.isEmpty())
1337      {
1338        changeTypes.add(PersistentSearchChangeType.ADD);
1339        changeTypes.add(PersistentSearchChangeType.DELETE);
1340        changeTypes.add(PersistentSearchChangeType.MODIFY);
1341        changeTypes.add(PersistentSearchChangeType.MODIFY_DN);
1342      }
1343
1344      if (tokenizer.hasMoreTokens())
1345      {
1346        String token = tokenizer.nextToken();
1347        if (token.equals("1") || token.equals("true") || token.equals("yes"))
1348        {
1349          changesOnly = true;
1350        }
1351        else if (token.equals("0") || token.equals("false") ||
1352                 token.equals("no"))
1353        {
1354          changesOnly = false;
1355        }
1356        else
1357        {
1358          printWrappedText(err, ERR_PSEARCH_INVALID_CHANGESONLY.get(token));
1359          return CLIENT_SIDE_PARAM_ERROR;
1360        }
1361      }
1362
1363      if (tokenizer.hasMoreTokens())
1364      {
1365        String token = tokenizer.nextToken();
1366        if (token.equals("1") || token.equals("true") || token.equals("yes"))
1367        {
1368          returnECs = true;
1369        }
1370        else if (token.equals("0") || token.equals("false") ||
1371                 token.equals("no"))
1372        {
1373          returnECs = false;
1374        }
1375        else
1376        {
1377          printWrappedText(err, ERR_PSEARCH_INVALID_RETURN_ECS.get(token));
1378          return CLIENT_SIDE_PARAM_ERROR;
1379        }
1380      }
1381
1382      PersistentSearchControl psearchControl =
1383           new PersistentSearchControl(changeTypes, changesOnly, returnECs);
1384      searchOptions.getControls().add(psearchControl);
1385    }
1386
1387    if (assertionFilter.isPresent())
1388    {
1389      String filterString = assertionFilter.getValue();
1390      LDAPFilter filter;
1391      try
1392      {
1393        filter = LDAPFilter.decode(filterString);
1394
1395        // FIXME -- Change this to the correct OID when the official one is
1396        //          assigned.
1397        Control assertionControl =
1398            new LDAPAssertionRequestControl(true, filter);
1399        searchOptions.getControls().add(assertionControl);
1400      }
1401      catch (LDAPException le)
1402      {
1403        printWrappedText(err, ERR_LDAP_ASSERTION_INVALID_FILTER.get(le.getMessage()));
1404        return CLIENT_SIDE_PARAM_ERROR;
1405      }
1406    }
1407
1408    if (matchedValuesFilter.isPresent())
1409    {
1410      LinkedList<String> mvFilterStrings = matchedValuesFilter.getValues();
1411      ArrayList<MatchedValuesFilter> mvFilters = new ArrayList<>();
1412      for (String s : mvFilterStrings)
1413      {
1414        try
1415        {
1416          LDAPFilter f = LDAPFilter.decode(s);
1417          mvFilters.add(MatchedValuesFilter.createFromLDAPFilter(f));
1418        }
1419        catch (LDAPException le)
1420        {
1421          printWrappedText(err, ERR_LDAP_MATCHEDVALUES_INVALID_FILTER.get(le.getMessage()));
1422          return CLIENT_SIDE_PARAM_ERROR;
1423        }
1424      }
1425
1426      MatchedValuesControl mvc = new MatchedValuesControl(true, mvFilters);
1427      searchOptions.getControls().add(mvc);
1428    }
1429
1430    if (sortOrder.isPresent())
1431    {
1432      try
1433      {
1434        searchOptions.getControls().add(
1435            new ServerSideSortRequestControl(sortOrder.getValue()));
1436      }
1437      catch (LDAPException le)
1438      {
1439        printWrappedText(err, ERR_LDAP_SORTCONTROL_INVALID_ORDER.get(le.getErrorMessage()));
1440        return CLIENT_SIDE_PARAM_ERROR;
1441      }
1442    }
1443
1444    if (vlvDescriptor.isPresent())
1445    {
1446      if (! sortOrder.isPresent())
1447      {
1448        printWrappedText(err,
1449            ERR_LDAPSEARCH_VLV_REQUIRES_SORT.get(vlvDescriptor.getLongIdentifier(), sortOrder.getLongIdentifier()));
1450        return CLIENT_SIDE_PARAM_ERROR;
1451      }
1452
1453      StringTokenizer tokenizer =
1454           new StringTokenizer(vlvDescriptor.getValue(), ":");
1455      int numTokens = tokenizer.countTokens();
1456      if (numTokens == 3)
1457      {
1458        try
1459        {
1460          int beforeCount = Integer.parseInt(tokenizer.nextToken());
1461          int afterCount  = Integer.parseInt(tokenizer.nextToken());
1462          ByteString assertionValue = ByteString.valueOfUtf8(tokenizer.nextToken());
1463          searchOptions.getControls().add(
1464              new VLVRequestControl(beforeCount, afterCount, assertionValue));
1465        }
1466        catch (Exception e)
1467        {
1468          printWrappedText(err, ERR_LDAPSEARCH_VLV_INVALID_DESCRIPTOR.get());
1469          return CLIENT_SIDE_PARAM_ERROR;
1470        }
1471      }
1472      else if (numTokens == 4)
1473      {
1474        try
1475        {
1476          int beforeCount  = Integer.parseInt(tokenizer.nextToken());
1477          int afterCount   = Integer.parseInt(tokenizer.nextToken());
1478          int offset       = Integer.parseInt(tokenizer.nextToken());
1479          int contentCount = Integer.parseInt(tokenizer.nextToken());
1480          searchOptions.getControls().add(
1481              new VLVRequestControl(beforeCount, afterCount, offset,
1482                  contentCount));
1483        }
1484        catch (Exception e)
1485        {
1486          printWrappedText(err, ERR_LDAPSEARCH_VLV_INVALID_DESCRIPTOR.get());
1487          return CLIENT_SIDE_PARAM_ERROR;
1488        }
1489      }
1490      else
1491      {
1492        printWrappedText(err, ERR_LDAPSEARCH_VLV_INVALID_DESCRIPTOR.get());
1493        return CLIENT_SIDE_PARAM_ERROR;
1494      }
1495    }
1496
1497    if (subEntriesArgument.isPresent())
1498    {
1499      Control subentriesControl =
1500          new SubentriesControl(true, true);
1501      searchOptions.getControls().add(subentriesControl);
1502    }
1503
1504    // Set the connection options.
1505    connectionOptions.setSASLExternal(saslExternal.isPresent());
1506    if(saslOptions.isPresent())
1507    {
1508      LinkedList<String> values = saslOptions.getValues();
1509      for(String saslOption : values)
1510      {
1511        if(saslOption.startsWith("mech="))
1512        {
1513          if (!connectionOptions.setSASLMechanism(saslOption))
1514          {
1515            return CLIENT_SIDE_PARAM_ERROR;
1516          }
1517        } else
1518        {
1519          if (!connectionOptions.addSASLProperty(saslOption))
1520          {
1521            return CLIENT_SIDE_PARAM_ERROR;
1522          }
1523        }
1524      }
1525    }
1526    connectionOptions.setUseSSL(useSSL.isPresent());
1527    connectionOptions.setStartTLS(startTLS.isPresent());
1528
1529    if(connectionOptions.useSASLExternal())
1530    {
1531      if(!connectionOptions.useSSL() && !connectionOptions.useStartTLS())
1532      {
1533        printWrappedText(err, ERR_TOOL_SASLEXTERNAL_NEEDS_SSL_OR_TLS.get());
1534        return CLIENT_SIDE_PARAM_ERROR;
1535      }
1536      if(keyStorePathValue == null)
1537      {
1538        printWrappedText(err, ERR_TOOL_SASLEXTERNAL_NEEDS_KEYSTORE.get());
1539        return CLIENT_SIDE_PARAM_ERROR;
1540      }
1541    }
1542
1543    connectionOptions.setVerbose(verbose.isPresent());
1544
1545    // Read the filter strings.
1546    if(fileNameValue != null)
1547    {
1548      BufferedReader in = null;
1549      try
1550      {
1551        in = new BufferedReader(new FileReader(fileNameValue));
1552        String line = null;
1553
1554        while ((line = in.readLine()) != null)
1555        {
1556          if(line.trim().equals(""))
1557          {
1558            // ignore empty lines.
1559            continue;
1560          }
1561          LDAPFilter ldapFilter = LDAPFilter.decode(line);
1562          filters.add(ldapFilter);
1563        }
1564      } catch(Exception e)
1565      {
1566        logger.traceException(e);
1567        printWrappedText(err, e.getMessage());
1568        return CLIENT_SIDE_PARAM_ERROR;
1569      }
1570      finally
1571      {
1572        close(in);
1573      }
1574    }
1575
1576    if(filters.isEmpty())
1577    {
1578      argParser.displayMessageAndUsageReference(err, ERR_SEARCH_NO_FILTERS.get());
1579      return CLIENT_SIDE_PARAM_ERROR;
1580    }
1581
1582    int wrapColumn = 80;
1583    if (dontWrap.isPresent())
1584    {
1585      wrapColumn = 0;
1586    }
1587
1588    LDAPSearch ldapSearch = null;
1589    try
1590    {
1591      if (initializeServer)
1592      {
1593        // Bootstrap and initialize directory data structures.
1594        EmbeddedUtils.initializeForClientUse();
1595      }
1596
1597      // Connect to the specified host with the supplied userDN and password.
1598      SSLConnectionFactory sslConnectionFactory = null;
1599      if(connectionOptions.useSSL() || connectionOptions.useStartTLS())
1600      {
1601        String clientAlias;
1602        if (certNickname.isPresent())
1603        {
1604          clientAlias = certNickname.getValue();
1605        }
1606        else
1607        {
1608          clientAlias = null;
1609        }
1610
1611        sslConnectionFactory = new SSLConnectionFactory();
1612        sslConnectionFactory.init(trustAll.isPresent(), keyStorePathValue,
1613                                  keyStorePasswordValue, clientAlias,
1614                                  trustStorePathValue, trustStorePasswordValue);
1615        connectionOptions.setSSLConnectionFactory(sslConnectionFactory);
1616      }
1617
1618      if (noop.isPresent())
1619      {
1620        // We don't actually need to open a connection or perform the search,
1621        // so we're done.  We should return 0 to either mean that the processing
1622        // was successful or that there were no matching entries, based on
1623        // countEntries.isPresent() (but in either case the return value should
1624        // be zero).
1625        return 0;
1626      }
1627
1628      AtomicInteger nextMessageID = new AtomicInteger(1);
1629      connection = new LDAPConnection(hostNameValue, portNumber,
1630                                      connectionOptions, out, err);
1631      int timeout = pSearchInfo.isPresent()?0:connectTimeout.getIntValue();
1632      connection.connectToHost(bindDNValue, bindPasswordValue, nextMessageID,
1633          timeout);
1634
1635
1636      int matchingEntries = 0;
1637      if (simplePageSize.isPresent())
1638      {
1639        if (filters.size() > 1)
1640        {
1641          LocalizableMessage message = ERR_PAGED_RESULTS_REQUIRES_SINGLE_FILTER.get();
1642          throw new LDAPException(CLIENT_SIDE_PARAM_ERROR, message);
1643        }
1644
1645        int pageSize = simplePageSize.getIntValue();
1646        ByteString cookieValue = null;
1647        ArrayList<Control> origControls = searchOptions.getControls();
1648
1649        while (true)
1650        {
1651          ArrayList<Control> newControls = new ArrayList<>(origControls.size() + 1);
1652          newControls.addAll(origControls);
1653          newControls.add(new PagedResultsControl(true, pageSize, cookieValue));
1654          searchOptions.setControls(newControls);
1655
1656          ldapSearch = new LDAPSearch(nextMessageID, out, err);
1657          matchingEntries += ldapSearch.executeSearch(connection, baseDNValue,
1658                                                      filters, attributes,
1659                                                      searchOptions,
1660                                                      wrapColumn);
1661
1662          List<Control> responseControls =
1663               ldapSearch.getResponseControls();
1664          boolean responseFound = false;
1665          for (Control c : responseControls)
1666          {
1667            if (c.getOID().equals(OID_PAGED_RESULTS_CONTROL))
1668            {
1669              try
1670              {
1671                PagedResultsControl control = PagedResultsControl.DECODER
1672                    .decode(c.isCritical(), ((LDAPControl) c).getValue());
1673                responseFound = true;
1674                cookieValue = control.getCookie();
1675                break;
1676              }
1677              catch (DirectoryException de)
1678              {
1679                LocalizableMessage message =
1680                    ERR_PAGED_RESULTS_CANNOT_DECODE.get(de.getMessage());
1681                throw new LDAPException(
1682                        CLIENT_SIDE_DECODING_ERROR, message, de);
1683              }
1684            }
1685          }
1686
1687          if (! responseFound)
1688          {
1689            LocalizableMessage message = ERR_PAGED_RESULTS_RESPONSE_NOT_FOUND.get();
1690            throw new LDAPException(CLIENT_SIDE_CONTROL_NOT_FOUND, message);
1691          }
1692          else if (cookieValue.length() == 0)
1693          {
1694            break;
1695          }
1696        }
1697      }
1698      else
1699      {
1700        ldapSearch = new LDAPSearch(nextMessageID, out, err);
1701        matchingEntries = ldapSearch.executeSearch(connection, baseDNValue,
1702                                                   filters, attributes,
1703                                                   searchOptions, wrapColumn);
1704      }
1705
1706      if (countEntries.isPresent() && returnMatchingEntries)
1707      {
1708        return matchingEntries;
1709      }
1710      else
1711      {
1712        return 0;
1713      }
1714
1715    } catch(LDAPException le)
1716    {
1717      int code = le.getResultCode();
1718      if (code == REFERRAL)
1719      {
1720        out.println();
1721        printWrappedText(out, le.getErrorMessage());
1722      }
1723      else
1724      {
1725      logger.traceException(le);
1726
1727        LDAPToolUtils.printErrorMessage(err, le.getMessageObject(), code,
1728            le.getErrorMessage(), le.getMatchedDN());
1729      }
1730      return code;
1731    } catch(LDAPConnectionException lce)
1732    {
1733      logger.traceException(lce);
1734      LDAPToolUtils.printErrorMessage(err,
1735                                      lce.getMessageObject(),
1736                                      lce.getResultCode(),
1737                                      lce.getErrorMessage(),
1738                                      lce.getMatchedDN());
1739      return lce.getResultCode();
1740    } catch(Exception e)
1741    {
1742      logger.traceException(e);
1743      printWrappedText(err, e.getMessage());
1744      return 1;
1745    } finally
1746    {
1747      if(connection != null)
1748      {
1749        if (ldapSearch == null)
1750        {
1751          connection.close(null);
1752        }
1753        else
1754        {
1755          connection.close(ldapSearch.nextMessageID);
1756        }
1757      }
1758    }
1759  }
1760
1761}
1762