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.CliMessages.INFO_DESCRIPTION_BINDPASSWORDFILE;
021import static com.forgerock.opendj.cli.Utils.*;
022import static com.forgerock.opendj.cli.CommonArguments.*;
023
024import static org.opends.messages.ToolMessages.*;
025import static org.opends.server.protocols.ldap.LDAPResultCode.*;
026import static org.opends.server.util.cli.LDAPConnectionArgumentParser.*;
027
028import java.io.BufferedReader;
029import java.io.FileReader;
030import java.io.IOException;
031import java.io.InputStreamReader;
032import java.io.OutputStream;
033import java.io.PrintStream;
034import java.io.Reader;
035import java.text.ParseException;
036import java.util.ArrayList;
037import java.util.List;
038import java.util.concurrent.atomic.AtomicInteger;
039
040import org.forgerock.i18n.LocalizableMessage;
041import org.forgerock.opendj.ldap.ByteString;
042import org.forgerock.opendj.ldap.DecodeException;
043import org.opends.server.controls.LDAPAssertionRequestControl;
044import org.opends.server.core.DirectoryServer.DirectoryServerVersionHandler;
045import org.opends.server.protocols.ldap.CompareRequestProtocolOp;
046import org.opends.server.protocols.ldap.CompareResponseProtocolOp;
047import org.opends.server.protocols.ldap.LDAPFilter;
048import org.opends.server.protocols.ldap.LDAPMessage;
049import org.opends.server.protocols.ldap.ProtocolOp;
050import org.opends.server.types.Control;
051import org.opends.server.types.LDAPException;
052import org.opends.server.types.NullOutputStream;
053import org.opends.server.util.Base64;
054import org.opends.server.util.EmbeddedUtils;
055
056import com.forgerock.opendj.cli.ArgumentException;
057import com.forgerock.opendj.cli.ArgumentParser;
058import com.forgerock.opendj.cli.BooleanArgument;
059import com.forgerock.opendj.cli.CliConstants;
060import com.forgerock.opendj.cli.ClientException;
061import com.forgerock.opendj.cli.FileBasedArgument;
062import com.forgerock.opendj.cli.IntegerArgument;
063import com.forgerock.opendj.cli.StringArgument;
064
065/**
066 * This class provides a tool that can be used to issue compare requests to the
067 * Directory Server.
068 */
069public class LDAPCompare
070{
071  /** The fully-qualified name of this class. */
072  private static final String CLASS_NAME =
073      "org.opends.server.tools.LDAPCompare";
074
075
076  /** The message ID counter to use for requests. */
077  private final AtomicInteger nextMessageID;
078
079  /** The print stream to use for standard error. */
080  private final PrintStream err;
081  /** The print stream to use for standard output. */
082  private final PrintStream out;
083
084  /** Tells whether the command-line is being executed in script friendly mode or not. */
085  private boolean isScriptFriendly;
086
087
088  /**
089   * Constructor for the LDAPCompare object.
090   *
091   * @param  nextMessageID  The message ID counter to use for requests.
092   * @param  out            The print stream to use for standard output.
093   * @param  err            The print stream to use for standard error.
094   */
095  public LDAPCompare(AtomicInteger nextMessageID, PrintStream out,
096                     PrintStream err)
097  {
098    this.nextMessageID = nextMessageID;
099    this.out           = out;
100    this.err           = err;
101  }
102
103  /**
104   * Execute the compare request in the specified list of DNs.
105   *
106   * @param connection      The connection to execute the request on.
107   * @param attributeType   The attribute type to compare.
108   * @param attributeVal    The attribute value to compare.
109   * @param lines           The list of DNs to compare the attribute in.
110   * @param compareOptions  The constraints for the compare request.
111   * @return the LDAP result code for the operation
112   *
113   * @throws  IOException  If a problem occurs while communicating with the
114   *                       Directory Server.
115   *
116   * @throws  LDAPException  If the server returns an error response.
117   */
118  public int readAndExecute(LDAPConnection connection, String attributeType,
119                             byte[] attributeVal, List<String> lines,
120                             LDAPCompareOptions compareOptions)
121         throws IOException, LDAPException
122  {
123    int aggResultCode = SUCCESS;
124    for(String line : lines)
125    {
126      int resultCode =
127          executeCompare(connection, attributeType, attributeVal, line,
128              compareOptions);
129      aggResultCode = aggregateResultCode(aggResultCode, resultCode);
130    }
131    return aggResultCode;
132  }
133
134
135  /**
136   * Read the specified DNs from the given reader
137   * (file or stdin) and execute the given compare request.
138   *
139   * @param connection      The connection to execute the request on.
140   * @param attributeType   The attribute type to compare.
141   * @param attributeVal    The attribute value to compare.
142   * @param reader          The reader to read the list of DNs from.
143   * @param compareOptions  The constraints for the compare request.
144   * @return the LDAP result code for the operation
145   *
146   * @throws  IOException  If a problem occurs while communicating with the
147   *                       Directory Server.
148   *
149   * @throws  LDAPException  If the server returns an error response.
150   */
151  public int readAndExecute(LDAPConnection connection, String attributeType,
152                             byte[] attributeVal, Reader reader,
153                             LDAPCompareOptions compareOptions)
154         throws IOException, LDAPException
155  {
156    int aggResultCode = 0;
157    BufferedReader in = new BufferedReader(reader);
158    String line = null;
159
160    while ((line = in.readLine()) != null)
161    {
162      int resultCode =
163          executeCompare(connection, attributeType, attributeVal, line,
164              compareOptions);
165      aggResultCode = aggregateResultCode(aggResultCode, resultCode);
166    }
167    in.close();
168    return aggResultCode;
169  }
170
171
172  /**
173   * Aggregates a new result code to the existing aggregated result codes. This
174   * method always overwrites the {@link LDAPResultCode#SUCCESS} and
175   * {@link LDAPResultCode#COMPARE_TRUE} result codes with the new result code.
176   * Then
177   *
178   * @param aggResultCodes
179   *          the aggregated result codes (a.k.a "accumulator")
180   * @param newResultCode
181   *          the new result code to aggregate
182   * @return the new aggregated result code
183   */
184  int aggregateResultCode(int aggResultCodes, int newResultCode)
185  {
186    if (aggResultCodes == SUCCESS || aggResultCodes == COMPARE_TRUE)
187    {
188      aggResultCodes = newResultCode;
189    }
190    else if (aggResultCodes == COMPARE_FALSE && newResultCode != COMPARE_TRUE)
191    {
192      aggResultCodes = newResultCode;
193    }
194    return aggResultCodes;
195  }
196
197
198  /**
199   * Execute the compare request for the specified DN entry.
200   *
201   * @param connection      The connection to execute the request on.
202   * @param attributeType   The attribute type to compare.
203   * @param attributeVal    The attribute value to compare.
204   * @param line            The DN to compare attribute in.
205   * @param compareOptions  The constraints for the compare request.
206   * @return the LDAP result code for the operation
207   *
208   * @throws  IOException  If a problem occurs while communicating with the
209   *                       Directory Server.
210   *
211   * @throws  LDAPException  If the server returns an error response.
212   */
213  private int executeCompare(LDAPConnection connection, String attributeType,
214                              byte[] attributeVal, String line,
215                              LDAPCompareOptions compareOptions)
216          throws IOException, LDAPException
217  {
218    ArrayList<Control> controls = compareOptions.getControls();
219    ByteString dnOctetStr = ByteString.valueOfUtf8(line);
220    ByteString attrValOctetStr = ByteString.wrap(attributeVal);
221
222    ProtocolOp protocolOp = new CompareRequestProtocolOp(dnOctetStr,
223                                     attributeType, attrValOctetStr);
224
225
226    if (!isScriptFriendly())
227    {
228      out.println(INFO_PROCESSING_COMPARE_OPERATION.get(
229          attributeType, attrValOctetStr, dnOctetStr));
230    }
231
232    if(!compareOptions.showOperations())
233    {
234      LDAPMessage responseMessage = null;
235      try
236      {
237        LDAPMessage message = new LDAPMessage(nextMessageID.getAndIncrement(),
238                                              protocolOp, controls);
239        connection.getLDAPWriter().writeMessage(message);
240        responseMessage = connection.getLDAPReader().readMessage();
241      } catch(DecodeException ae)
242      {
243        if (!compareOptions.continueOnError())
244        {
245          String message = LDAPToolUtils.getMessageForConnectionException(ae);
246          throw new IOException(message, ae);
247        }
248        else
249        {
250          printWrappedText(err, INFO_OPERATION_FAILED.get("COMPARE"));
251          printWrappedText(err, ae.getMessage());
252          return OPERATIONS_ERROR;
253        }
254      }
255
256      CompareResponseProtocolOp op =
257        responseMessage.getCompareResponseProtocolOp();
258      int resultCode = op.getResultCode();
259      LocalizableMessage errorMessage = op.getErrorMessage();
260
261      if(resultCode != COMPARE_TRUE && resultCode != COMPARE_FALSE
262         && !compareOptions.continueOnError())
263      {
264        LocalizableMessage msg = INFO_OPERATION_FAILED.get("COMPARE");
265        throw new LDAPException(resultCode, errorMessage, msg,
266                                op.getMatchedDN(), null);
267      } else
268      {
269        if(resultCode == COMPARE_FALSE)
270        {
271          if (isScriptFriendly())
272          {
273            out.println(line+": "+COMPARE_FALSE);
274          }
275          else
276          {
277            out.println(INFO_COMPARE_OPERATION_RESULT_FALSE.get(line));
278          }
279        } else if(resultCode == COMPARE_TRUE)
280        {
281          if (isScriptFriendly())
282          {
283            out.println(line+": "+COMPARE_TRUE);
284          }
285          else
286          {
287            out.println(INFO_COMPARE_OPERATION_RESULT_TRUE.get(line));
288          }
289        } else
290        {
291          LocalizableMessage msg = INFO_OPERATION_FAILED.get("COMPARE");
292          LDAPToolUtils.printErrorMessage(err, msg, resultCode, errorMessage,
293                                          op.getMatchedDN());
294        }
295      }
296      return resultCode;
297    }
298    return SUCCESS;
299  }
300
301  /**
302   * The main method for LDAPCompare tool.
303   *
304   * @param  args  The command-line arguments provided to this program.
305   */
306  public static void main(String[] args)
307  {
308    int retCode = mainCompare(args, true, System.out, System.err);
309    if(retCode != 0)
310    {
311      System.exit(filterExitCode(retCode));
312    }
313  }
314
315  /**
316   * Parses the provided command-line arguments and uses that information to
317   * run the ldapcompare tool.
318   *
319   * @param  args  The command-line arguments provided to this program.
320   *
321   * @return The error code.
322   */
323  public static int mainCompare(String[] args)
324  {
325    return mainCompare(args, true, System.out, System.err);
326  }
327
328  /**
329   * Parses the provided command-line arguments and uses that information to
330   * run the ldapcompare tool.
331   *
332   * @param  args              The command-line arguments provided to this
333   *                           program.
334   * @param  initializeServer  Indicates whether to initialize the server.
335   * @param  outStream         The output stream to use for standard output, or
336   *                           <CODE>null</CODE> if standard output is not
337   *                           needed.
338   * @param  errStream         The output stream to use for standard error, or
339   *                           <CODE>null</CODE> if standard error is not
340   *                           needed.
341   *
342   * @return The error code.
343   */
344  public static int mainCompare(String[] args, boolean initializeServer,
345                                OutputStream outStream, OutputStream errStream)
346  {
347    PrintStream out = NullOutputStream.wrapOrNullStream(outStream);
348    PrintStream err = NullOutputStream.wrapOrNullStream(errStream);
349
350    LDAPConnectionOptions connectionOptions = new LDAPConnectionOptions();
351    LDAPCompareOptions compareOptions = new LDAPCompareOptions();
352    LDAPConnection connection = null;
353
354    final BooleanArgument continueOnError;
355    final BooleanArgument noop;
356    final BooleanArgument saslExternal;
357    final BooleanArgument showUsage;
358    final BooleanArgument useCompareResultCode;
359    final BooleanArgument startTLS;
360    final BooleanArgument trustAll;
361    final BooleanArgument useSSL;
362    final BooleanArgument verbose;
363    final FileBasedArgument bindPasswordFile;
364    final FileBasedArgument keyStorePasswordFile;
365    final FileBasedArgument trustStorePasswordFile;
366    final IntegerArgument port;
367    final IntegerArgument version;
368    final StringArgument assertionFilter;
369    final StringArgument bindDN;
370    final StringArgument bindPassword;
371    final StringArgument certNickname;
372    final StringArgument controlStr;
373    final StringArgument encodingStr;
374    final StringArgument filename;
375    final StringArgument hostName;
376    final StringArgument keyStorePath;
377    final StringArgument keyStorePassword;
378    final StringArgument saslOptions;
379    final StringArgument trustStorePath;
380    final StringArgument trustStorePassword;
381    final IntegerArgument connectTimeout;
382    final StringArgument propertiesFileArgument;
383    final BooleanArgument noPropertiesFileArgument;
384    BooleanArgument scriptFriendlyArgument = null;
385
386    final List<String> dnStrings = new ArrayList<> ();
387    final String attributeType;
388    final byte[] attributeVal;
389    Reader rdr = null;
390
391    // Create the command-line argument parser for use with this program.
392    LocalizableMessage toolDescription = INFO_LDAPCOMPARE_TOOL_DESCRIPTION.get();
393    ArgumentParser argParser = new ArgumentParser(CLASS_NAME, toolDescription,
394                                        false, true, 1, 0,
395                                        " \'attribute:value\' \"DN\" ...");
396    argParser.setShortToolDescription(REF_SHORT_DESC_LDAPCOMPARE.get());
397    argParser.setVersionHandler(new DirectoryServerVersionHandler());
398
399    try
400    {
401      scriptFriendlyArgument =
402              BooleanArgument.builder("script-friendly")
403                      .shortIdentifier('s')
404                      .description(INFO_DESCRIPTION_SCRIPT_FRIENDLY.get())
405                      .buildAndAddToParser(argParser);
406      propertiesFileArgument =
407              StringArgument.builder(OPTION_LONG_PROP_FILE_PATH)
408                      .description(INFO_DESCRIPTION_PROP_FILE_PATH.get())
409                      .valuePlaceholder(INFO_PROP_FILE_PATH_PLACEHOLDER.get())
410                      .buildAndAddToParser(argParser);
411      argParser.setFilePropertiesArgument(propertiesFileArgument);
412
413      noPropertiesFileArgument =
414              BooleanArgument.builder(OPTION_LONG_NO_PROP_FILE)
415                      .description(INFO_DESCRIPTION_NO_PROP_FILE.get())
416                      .buildAndAddToParser(argParser);
417      argParser.setNoPropertiesFileArgument(noPropertiesFileArgument);
418
419      hostName =
420              StringArgument.builder(OPTION_LONG_HOST)
421                      .shortIdentifier(OPTION_SHORT_HOST)
422                      .description(INFO_DESCRIPTION_HOST.get())
423                      .defaultValue("localhost")
424                      .valuePlaceholder(INFO_HOST_PLACEHOLDER.get())
425                      .buildAndAddToParser(argParser);
426      port =
427              IntegerArgument.builder(OPTION_LONG_PORT)
428                      .shortIdentifier(OPTION_SHORT_PORT)
429                      .description(INFO_DESCRIPTION_PORT.get())
430                      .range(1, 65535)
431                      .defaultValue(389)
432                      .valuePlaceholder(INFO_PORT_PLACEHOLDER.get())
433                      .buildAndAddToParser(argParser);
434      useSSL =
435              BooleanArgument.builder(OPTION_LONG_USE_SSL)
436                      .shortIdentifier(OPTION_SHORT_USE_SSL)
437                      .description(INFO_DESCRIPTION_USE_SSL.get())
438                      .buildAndAddToParser(argParser);
439      startTLS =
440              BooleanArgument.builder(OPTION_LONG_START_TLS)
441                      .shortIdentifier(OPTION_SHORT_START_TLS)
442                      .description(INFO_DESCRIPTION_START_TLS.get())
443                      .buildAndAddToParser(argParser);
444      bindDN =
445              StringArgument.builder(OPTION_LONG_BINDDN)
446                      .shortIdentifier(OPTION_SHORT_BINDDN)
447                      .description(INFO_DESCRIPTION_BINDDN.get())
448                      .valuePlaceholder(INFO_BINDDN_PLACEHOLDER.get())
449                      .buildAndAddToParser(argParser);
450      bindPassword =
451              StringArgument.builder(OPTION_LONG_BINDPWD)
452                      .shortIdentifier(OPTION_SHORT_BINDPWD)
453                      .description(INFO_DESCRIPTION_BINDPASSWORD.get())
454                      .valuePlaceholder(INFO_BINDPWD_PLACEHOLDER.get())
455                      .buildAndAddToParser(argParser);
456      bindPasswordFile =
457              FileBasedArgument.builder(OPTION_LONG_BINDPWD_FILE)
458                      .shortIdentifier(OPTION_SHORT_BINDPWD_FILE)
459                      .description(INFO_DESCRIPTION_BINDPASSWORDFILE.get())
460                      .valuePlaceholder(INFO_BINDPWD_FILE_PLACEHOLDER.get())
461                      .buildAndAddToParser(argParser);
462      filename =
463              StringArgument.builder(OPTION_LONG_FILENAME)
464                      .shortIdentifier(OPTION_SHORT_FILENAME)
465                      .description(INFO_COMPARE_DESCRIPTION_FILENAME.get())
466                      .valuePlaceholder(INFO_FILE_PLACEHOLDER.get())
467                      .buildAndAddToParser(argParser);
468      saslExternal =
469              BooleanArgument.builder("useSASLExternal")
470                      .shortIdentifier('r')
471                      .description(INFO_DESCRIPTION_USE_SASL_EXTERNAL.get())
472                      .buildAndAddToParser(argParser);
473      saslOptions =
474              StringArgument.builder(OPTION_LONG_SASLOPTION)
475                      .shortIdentifier(OPTION_SHORT_SASLOPTION)
476                      .description(INFO_DESCRIPTION_SASL_PROPERTIES.get())
477                      .multiValued()
478                      .valuePlaceholder(INFO_SASL_OPTION_PLACEHOLDER.get())
479                      .buildAndAddToParser(argParser);
480
481      trustAll = trustAllArgument();
482      argParser.addArgument(trustAll);
483
484      keyStorePath =
485              StringArgument.builder(OPTION_LONG_KEYSTOREPATH)
486                      .shortIdentifier(OPTION_SHORT_KEYSTOREPATH)
487                      .description(INFO_DESCRIPTION_KEYSTOREPATH.get())
488                      .valuePlaceholder(INFO_KEYSTOREPATH_PLACEHOLDER.get())
489                      .buildAndAddToParser(argParser);
490      keyStorePassword =
491              StringArgument.builder(OPTION_LONG_KEYSTORE_PWD)
492                      .shortIdentifier(OPTION_SHORT_KEYSTORE_PWD)
493                      .description(INFO_DESCRIPTION_KEYSTOREPASSWORD.get())
494                      .valuePlaceholder(INFO_KEYSTORE_PWD_PLACEHOLDER.get())
495                      .buildAndAddToParser(argParser);
496      keyStorePasswordFile =
497              FileBasedArgument.builder(OPTION_LONG_KEYSTORE_PWD_FILE)
498                      .shortIdentifier(OPTION_SHORT_KEYSTORE_PWD_FILE)
499                      .description(INFO_DESCRIPTION_KEYSTOREPASSWORD_FILE.get())
500                      .valuePlaceholder(INFO_KEYSTORE_PWD_FILE_PLACEHOLDER.get())
501                      .buildAndAddToParser(argParser);
502      certNickname =
503              StringArgument.builder("certNickname")
504                      .shortIdentifier('N')
505                      .description(INFO_DESCRIPTION_CERT_NICKNAME.get())
506                      .valuePlaceholder(INFO_NICKNAME_PLACEHOLDER.get())
507                      .buildAndAddToParser(argParser);
508      trustStorePath =
509              StringArgument.builder(OPTION_LONG_TRUSTSTOREPATH)
510                      .shortIdentifier(OPTION_SHORT_TRUSTSTOREPATH)
511                      .description(INFO_DESCRIPTION_TRUSTSTOREPATH.get())
512                      .valuePlaceholder(INFO_TRUSTSTOREPATH_PLACEHOLDER.get())
513                      .buildAndAddToParser(argParser);
514      trustStorePassword =
515              StringArgument.builder(OPTION_LONG_TRUSTSTORE_PWD)
516                      .description(INFO_DESCRIPTION_TRUSTSTOREPASSWORD.get())
517                      .valuePlaceholder(INFO_TRUSTSTORE_PWD_PLACEHOLDER.get())
518                      .buildAndAddToParser(argParser);
519      trustStorePasswordFile =
520              FileBasedArgument.builder(OPTION_LONG_TRUSTSTORE_PWD_FILE)
521                      .shortIdentifier(OPTION_SHORT_TRUSTSTORE_PWD_FILE)
522                      .description(INFO_DESCRIPTION_TRUSTSTOREPASSWORD_FILE.get())
523                      .valuePlaceholder(INFO_TRUSTSTORE_PWD_FILE_PLACEHOLDER.get())
524                      .buildAndAddToParser(argParser);
525      assertionFilter =
526              StringArgument.builder(OPTION_LONG_ASSERTION_FILE)
527                      .description(INFO_DESCRIPTION_ASSERTION_FILTER.get())
528                      .valuePlaceholder(INFO_ASSERTION_FILTER_PLACEHOLDER.get())
529                      .buildAndAddToParser(argParser);
530      controlStr =
531              StringArgument.builder("control")
532                      .shortIdentifier('J')
533                      .description(INFO_DESCRIPTION_CONTROLS.get())
534                      .multiValued()
535                      .valuePlaceholder(INFO_LDAP_CONTROL_PLACEHOLDER.get())
536                      .buildAndAddToParser(argParser);
537      version =
538              IntegerArgument.builder(OPTION_LONG_PROTOCOL_VERSION)
539                      .shortIdentifier(OPTION_SHORT_PROTOCOL_VERSION)
540                      .description(INFO_DESCRIPTION_VERSION.get())
541                      .defaultValue(3)
542                      .valuePlaceholder(INFO_PROTOCOL_VERSION_PLACEHOLDER.get())
543                      .buildAndAddToParser(argParser);
544      connectTimeout =
545              IntegerArgument.builder(OPTION_LONG_CONNECT_TIMEOUT)
546                      .description(INFO_DESCRIPTION_CONNECTION_TIMEOUT.get())
547                      .lowerBound(0)
548                      .defaultValue(CliConstants.DEFAULT_LDAP_CONNECT_TIMEOUT)
549                      .valuePlaceholder(INFO_TIMEOUT_PLACEHOLDER.get())
550                      .buildAndAddToParser(argParser);
551      encodingStr =
552              StringArgument.builder("encoding")
553                      .shortIdentifier('i')
554                      .description(INFO_DESCRIPTION_ENCODING.get())
555                      .valuePlaceholder(INFO_ENCODING_PLACEHOLDER.get())
556                      .buildAndAddToParser(argParser);
557      continueOnError =
558              BooleanArgument.builder("continueOnError")
559                      .shortIdentifier('c')
560                      .description(INFO_DESCRIPTION_CONTINUE_ON_ERROR.get())
561                      .buildAndAddToParser(argParser);
562      noop =
563              BooleanArgument.builder(OPTION_LONG_DRYRUN)
564                      .shortIdentifier(OPTION_SHORT_DRYRUN)
565                      .description(INFO_DESCRIPTION_NOOP.get())
566                      .buildAndAddToParser(argParser);
567
568      verbose = verboseArgument();
569      argParser.addArgument(verbose);
570
571      showUsage = showUsageArgument();
572      argParser.addArgument(showUsage);
573
574      useCompareResultCode =
575              BooleanArgument.builder("useCompareResultCode")
576                      .shortIdentifier('m')
577                      .description(INFO_LDAPCOMPARE_DESCRIPTION_USE_COMPARE_RESULT.get())
578                      .buildAndAddToParser(argParser);
579
580      argParser.setUsageArgument(showUsage, out);
581    } catch (ArgumentException ae)
582    {
583      printWrappedText(err, ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage()));
584      return CLIENT_SIDE_PARAM_ERROR;
585    }
586
587    // Parse the command-line arguments provided to this program.
588    try
589    {
590      argParser.parseArguments(args);
591    }
592    catch (ArgumentException ae)
593    {
594      argParser.displayMessageAndUsageReference(err, ERR_ERROR_PARSING_ARGS.get(ae.getMessage()));
595      return CLIENT_SIDE_PARAM_ERROR;
596    }
597
598    // If we should just display usage or version information,
599    // then print it and exit.
600    if (argParser.usageOrVersionDisplayed())
601    {
602      return SUCCESS;
603    }
604
605    if (bindPassword.isPresent() && bindPasswordFile.isPresent())
606    {
607      printWrappedText(err, conflictingArgsErrorMessage(bindPassword, bindPasswordFile));
608      return CLIENT_SIDE_PARAM_ERROR;
609    }
610
611    ArrayList<String> attrAndDNStrings = argParser.getTrailingArguments();
612
613    if(attrAndDNStrings.isEmpty())
614    {
615      printWrappedText(err, ERR_LDAPCOMPARE_NO_ATTR.get());
616      return CLIENT_SIDE_PARAM_ERROR;
617    }
618
619    // First element should be an attribute string.
620    String attributeString = attrAndDNStrings.remove(0);
621    // Rest are DN strings
622    dnStrings.addAll(attrAndDNStrings);
623
624    // If no DNs were provided, then exit with an error.
625    if (dnStrings.isEmpty() && !filename.isPresent())
626    {
627      printWrappedText(err, ERR_LDAPCOMPARE_NO_DNS.get());
628      return CLIENT_SIDE_PARAM_ERROR;
629    }
630
631    // If trailing DNs were provided and the filename argument was also
632    // provided, exit with an error.
633    if (!dnStrings.isEmpty() && filename.isPresent())
634    {
635      printWrappedText(err, ERR_LDAPCOMPARE_FILENAME_AND_DNS.get());
636      return CLIENT_SIDE_PARAM_ERROR;
637    }
638
639    // parse the attribute string
640    int idx = attributeString.indexOf(":");
641    if(idx == -1)
642    {
643      printWrappedText(err, ERR_LDAPCOMPARE_INVALID_ATTR_STRING.get(attributeString));
644      return CLIENT_SIDE_PARAM_ERROR;
645    }
646    attributeType = attributeString.substring(0, idx);
647    String remainder = attributeString.substring(idx+1,
648                                                 attributeString.length());
649    if (remainder.length() > 0)
650    {
651      char nextChar = remainder.charAt(0);
652      if(nextChar == ':')
653      {
654        String base64 = remainder.substring(1, remainder.length());
655        try
656        {
657          attributeVal = Base64.decode(base64);
658        }
659        catch (ParseException e)
660        {
661          printWrappedText(err, INFO_COMPARE_CANNOT_BASE64_DECODE_ASSERTION_VALUE.get());
662          printWrappedText(err, e.getLocalizedMessage());
663          return CLIENT_SIDE_PARAM_ERROR;
664        }
665      } else if(nextChar == '<')
666      {
667        try
668        {
669          String filePath = remainder.substring(1, remainder.length());
670          attributeVal = LDAPToolUtils.readBytesFromFile(filePath, err);
671        }
672        catch (Exception e)
673        {
674          printWrappedText(err, INFO_COMPARE_CANNOT_READ_ASSERTION_VALUE_FROM_FILE.get(e));
675          return CLIENT_SIDE_PARAM_ERROR;
676        }
677      } else
678      {
679        attributeVal = remainder.getBytes();
680      }
681    }
682    else
683    {
684      attributeVal = remainder.getBytes();
685    }
686
687    String hostNameValue = hostName.getValue();
688    int portNumber = 389;
689    try
690    {
691      portNumber = port.getIntValue();
692    } catch (ArgumentException ae)
693    {
694      argParser.displayMessageAndUsageReference(err, ae.getMessageObject());
695      return CLIENT_SIDE_PARAM_ERROR;
696    }
697
698    try
699    {
700      int versionNumber = version.getIntValue();
701      if(versionNumber != 2 && versionNumber != 3)
702      {
703        printWrappedText(err, ERR_DESCRIPTION_INVALID_VERSION.get(versionNumber));
704        return CLIENT_SIDE_PARAM_ERROR;
705      }
706      connectionOptions.setVersionNumber(versionNumber);
707    } catch(ArgumentException ae)
708    {
709      argParser.displayMessageAndUsageReference(err, ae.getMessageObject());
710      return CLIENT_SIDE_PARAM_ERROR;
711    }
712
713
714    String bindDNValue = bindDN.getValue();
715    String fileNameValue = filename.getValue();
716    String bindPasswordValue;
717    try
718    {
719      bindPasswordValue = getPasswordValue(
720          bindPassword, bindPasswordFile, bindDNValue, out, err);
721    }
722    catch (ClientException ex)
723    {
724      printWrappedText(err, ex.getMessage());
725      return CLIENT_SIDE_PARAM_ERROR;
726    }
727
728    String keyStorePathValue = keyStorePath.getValue();
729    String trustStorePathValue = trustStorePath.getValue();
730
731    String keyStorePasswordValue = null;
732    if (keyStorePassword.isPresent())
733    {
734      keyStorePasswordValue = keyStorePassword.getValue();
735    }
736    else if (keyStorePasswordFile.isPresent())
737    {
738      keyStorePasswordValue = keyStorePasswordFile.getValue();
739    }
740
741    String trustStorePasswordValue = null;
742    if (trustStorePassword.isPresent())
743    {
744      trustStorePasswordValue = trustStorePassword.getValue();
745    }
746    else if (trustStorePasswordFile.isPresent())
747    {
748      trustStorePasswordValue = trustStorePasswordFile.getValue();
749    }
750
751    compareOptions.setShowOperations(noop.isPresent());
752    compareOptions.setVerbose(verbose.isPresent());
753    compareOptions.setContinueOnError(continueOnError.isPresent());
754    compareOptions.setEncoding(encodingStr.getValue());
755
756    if(controlStr.isPresent())
757    {
758      for (String ctrlString : controlStr.getValues())
759      {
760        Control ctrl = LDAPToolUtils.getControl(ctrlString, err);
761        if(ctrl == null)
762        {
763          printWrappedText(err, ERR_TOOL_INVALID_CONTROL_STRING.get(ctrlString));
764          return CLIENT_SIDE_PARAM_ERROR;
765        }
766        compareOptions.getControls().add(ctrl);
767      }
768    }
769
770    if (assertionFilter.isPresent())
771    {
772      String filterString = assertionFilter.getValue();
773      LDAPFilter filter;
774      try
775      {
776        filter = LDAPFilter.decode(filterString);
777
778        Control assertionControl =
779            new LDAPAssertionRequestControl(true, filter);
780        compareOptions.getControls().add(assertionControl);
781      }
782      catch (LDAPException le)
783      {
784        printWrappedText(err, ERR_LDAP_ASSERTION_INVALID_FILTER.get(le.getMessage()));
785        return CLIENT_SIDE_PARAM_ERROR;
786      }
787    }
788
789    // Set the connection options.
790    // Parse the SASL properties.
791    connectionOptions.setSASLExternal(saslExternal.isPresent());
792    if(saslOptions.isPresent())
793    {
794      for (String saslOption : saslOptions.getValues())
795      {
796        boolean val;
797        if(saslOption.startsWith("mech="))
798        {
799          val = connectionOptions.setSASLMechanism(saslOption);
800        }
801        else
802        {
803          val = connectionOptions.addSASLProperty(saslOption);
804        }
805        if(!val)
806        {
807          return CLIENT_SIDE_PARAM_ERROR;
808        }
809      }
810    }
811    connectionOptions.setUseSSL(useSSL.isPresent());
812    connectionOptions.setStartTLS(startTLS.isPresent());
813
814    if(connectionOptions.useSASLExternal())
815    {
816      if(!connectionOptions.useSSL() && !connectionOptions.useStartTLS())
817      {
818        printWrappedText(err, ERR_TOOL_SASLEXTERNAL_NEEDS_SSL_OR_TLS.get());
819        return CLIENT_SIDE_PARAM_ERROR;
820      }
821      if(keyStorePathValue == null)
822      {
823        printWrappedText(err, ERR_TOOL_SASLEXTERNAL_NEEDS_KEYSTORE.get());
824        return CLIENT_SIDE_PARAM_ERROR;
825      }
826    }
827
828    LDAPCompare ldapCompare = null;
829    try
830    {
831      if (initializeServer)
832      {
833        // Bootstrap and initialize directory data structures.
834        EmbeddedUtils.initializeForClientUse();
835      }
836
837      // Connect to the specified host with the supplied userDN and password.
838      SSLConnectionFactory sslConnectionFactory = null;
839      if(connectionOptions.useSSL() || connectionOptions.useStartTLS())
840      {
841        String clientAlias;
842        if (certNickname.isPresent())
843        {
844          clientAlias = certNickname.getValue();
845        }
846        else
847        {
848          clientAlias = null;
849        }
850
851        sslConnectionFactory = new SSLConnectionFactory();
852        sslConnectionFactory.init(trustAll.isPresent(), keyStorePathValue,
853                                  keyStorePasswordValue, clientAlias,
854                                  trustStorePathValue, trustStorePasswordValue);
855        connectionOptions.setSSLConnectionFactory(sslConnectionFactory);
856      }
857
858      AtomicInteger nextMessageID = new AtomicInteger(1);
859      connection = new LDAPConnection(hostNameValue, portNumber,
860                                      connectionOptions, out, err);
861
862      int timeout = connectTimeout.getIntValue();
863      connection.connectToHost(bindDNValue, bindPasswordValue, nextMessageID,
864          timeout);
865
866      ldapCompare = new LDAPCompare(nextMessageID, out, err);
867      ldapCompare.isScriptFriendly = scriptFriendlyArgument.isPresent();
868      if(fileNameValue == null && dnStrings.isEmpty())
869      {
870        // Read from stdin.
871        rdr = new InputStreamReader(System.in);
872      } else if(fileNameValue != null)
873      {
874        try
875        {
876          rdr = new FileReader(fileNameValue);
877        }
878        catch (Throwable t)
879        {
880          String details = t.getMessage();
881          if (details == null)
882          {
883            details = t.toString();
884          }
885          printWrappedText(err, ERR_LDAPCOMPARE_ERROR_READING_FILE.get(fileNameValue, details));
886          return CLIENT_SIDE_PARAM_ERROR;
887        }
888      }
889      int resultCode;
890      if(rdr != null)
891      {
892        resultCode =
893            ldapCompare.readAndExecute(connection, attributeType, attributeVal,
894                rdr, compareOptions);
895      } else
896      {
897        resultCode =
898            ldapCompare.readAndExecute(connection, attributeType, attributeVal,
899                dnStrings, compareOptions);
900      }
901
902      if (useCompareResultCode.isPresent())
903      {
904        return resultCode;
905      }
906      return SUCCESS;
907    } catch(LDAPException le)
908    {
909      LDAPToolUtils.printErrorMessage(
910              err, le.getMessageObject(),
911              le.getResultCode(),
912              le.getMessageObject(),
913              le.getMatchedDN());
914      return le.getResultCode();
915    } catch(LDAPConnectionException lce)
916    {
917      LDAPToolUtils.printErrorMessage(err,
918                                      lce.getMessageObject(),
919                                      lce.getResultCode(),
920                                      lce.getMessageObject(),
921                                      lce.getMatchedDN());
922      return lce.getResultCode();
923    } catch(Exception e)
924    {
925      printWrappedText(err, e.getMessage());
926      return OPERATIONS_ERROR;
927    } finally
928    {
929      if(connection != null)
930      {
931        if (ldapCompare != null)
932        {
933          connection.close(ldapCompare.nextMessageID);
934        }
935        else
936        {
937          connection.close(null);
938        }
939      }
940    }
941  }
942
943  private boolean isScriptFriendly()
944  {
945    return isScriptFriendly;
946  }
947}