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