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 profiq, s.r.o.
016 * Portions Copyright 2012-2016 ForgeRock AS.
017 */
018package org.opends.server.tools;
019
020import static com.forgerock.opendj.cli.ArgumentConstants.*;
021import static com.forgerock.opendj.cli.CliMessages.INFO_DESCRIPTION_BINDPASSWORDFILE;
022import static com.forgerock.opendj.cli.Utils.*;
023import static com.forgerock.opendj.cli.CommonArguments.*;
024
025import static org.opends.messages.ToolMessages.*;
026import static org.opends.server.protocols.ldap.LDAPResultCode.*;
027import static org.opends.server.util.ServerConstants.*;
028import static org.opends.server.util.cli.LDAPConnectionArgumentParser.*;
029
030import java.io.FileInputStream;
031import java.io.FileNotFoundException;
032import java.io.IOException;
033import java.io.InputStream;
034import java.io.OutputStream;
035import java.io.PrintStream;
036import java.util.*;
037import java.util.concurrent.atomic.AtomicInteger;
038
039import org.forgerock.i18n.LocalizableMessage;
040import org.forgerock.i18n.slf4j.LocalizedLogger;
041import org.forgerock.opendj.ldap.ByteString;
042import org.forgerock.opendj.ldap.DN;
043import org.forgerock.opendj.ldap.DecodeException;
044import org.forgerock.opendj.ldap.ResultCode;
045import org.opends.server.controls.*;
046import org.opends.server.core.DirectoryServer.DirectoryServerVersionHandler;
047import org.opends.server.plugins.ChangeNumberControlPlugin;
048import org.opends.server.protocols.ldap.AddRequestProtocolOp;
049import org.opends.server.protocols.ldap.AddResponseProtocolOp;
050import org.opends.server.protocols.ldap.DeleteRequestProtocolOp;
051import org.opends.server.protocols.ldap.DeleteResponseProtocolOp;
052import org.opends.server.protocols.ldap.ExtendedResponseProtocolOp;
053import org.opends.server.protocols.ldap.LDAPAttribute;
054import org.opends.server.protocols.ldap.LDAPConstants;
055import org.opends.server.protocols.ldap.LDAPControl;
056import org.opends.server.protocols.ldap.LDAPFilter;
057import org.opends.server.protocols.ldap.LDAPMessage;
058import org.opends.server.protocols.ldap.ModifyDNRequestProtocolOp;
059import org.opends.server.protocols.ldap.ModifyDNResponseProtocolOp;
060import org.opends.server.protocols.ldap.ModifyRequestProtocolOp;
061import org.opends.server.protocols.ldap.ModifyResponseProtocolOp;
062import org.opends.server.protocols.ldap.ProtocolOp;
063import org.opends.server.types.*;
064import org.opends.server.util.AddChangeRecordEntry;
065import org.opends.server.util.ChangeRecordEntry;
066import org.opends.server.util.EmbeddedUtils;
067import org.opends.server.util.LDIFException;
068import org.opends.server.util.LDIFReader;
069import org.opends.server.util.ModifyChangeRecordEntry;
070import org.opends.server.util.ModifyDNChangeRecordEntry;
071
072import com.forgerock.opendj.cli.ArgumentException;
073import com.forgerock.opendj.cli.ArgumentParser;
074import com.forgerock.opendj.cli.BooleanArgument;
075import com.forgerock.opendj.cli.CliConstants;
076import com.forgerock.opendj.cli.FileBasedArgument;
077import com.forgerock.opendj.cli.IntegerArgument;
078import com.forgerock.opendj.cli.StringArgument;
079
080/**
081 * This class provides a tool that can be used to issue modify requests to the
082 * Directory Server.
083 */
084public class LDAPModify
085{
086  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
087
088  /**
089   * The fully-qualified name of this class.
090   */
091  private static final String CLASS_NAME = "org.opends.server.tools.LDAPModify";
092
093  /** The message ID counter to use for requests. */
094  private final AtomicInteger nextMessageID;
095
096  /** The print stream to use for standard error. */
097  private final PrintStream err;
098
099  /** The print stream to use for standard output. */
100  private final PrintStream out;
101
102  /**
103   * Constructor for the LDAPModify object.
104   *
105   * @param  nextMessageID  The message ID counter to use for requests.
106   * @param  out            The print stream to use for standard output.
107   * @param  err            The print stream to use for standard error.
108   */
109  public LDAPModify(AtomicInteger nextMessageID, PrintStream out,
110      PrintStream err)
111  {
112    this.nextMessageID = nextMessageID;
113    this.out           = out;
114    this.err           = err;
115  }
116
117
118  /**
119   * Read the specified change records from the given input stream
120   * (file or stdin) and execute the given modify request.
121   *
122   * @param connection     The connection to use for this modify request.
123   * @param fileNameValue  Name of the file from which to read.  If null,
124   *                       input will be read from <code>System.in</code>.
125   * @param modifyOptions  The constraints for the modify request.
126   *
127   * @throws  IOException  If a problem occurs while attempting to communicate
128   *                       with the Directory Server.
129   *
130   * @throws  LDAPException  If the Directory Server returns an error response.
131   */
132  public void readAndExecute(LDAPConnection connection, String fileNameValue,
133                             LDAPModifyOptions modifyOptions)
134         throws IOException, LDAPException
135  {
136    ArrayList<Control> controls = modifyOptions.getControls();
137    LDIFReader reader;
138
139    // Create an LDIF import configuration to do this and then get the reader.
140
141    try
142    {
143      InputStream is = System.in;
144      if(fileNameValue != null)
145      {
146        is = new FileInputStream(fileNameValue);
147      }
148
149      LDIFImportConfig importConfig = new LDIFImportConfig(is);
150      importConfig.setValidateSchema(false);
151      reader = new LDIFReader(importConfig);
152    } catch (Exception e)
153    {
154      logger.traceException(e);
155      LocalizableMessage message =
156          ERR_LDIF_FILE_CANNOT_OPEN_FOR_READ.get(fileNameValue,
157                  e.getLocalizedMessage());
158      throw new FileNotFoundException(message.toString());
159    }
160
161    // Set this for error messages
162    if (fileNameValue == null)
163    {
164      fileNameValue = "Console";
165    }
166
167    while (true)
168    {
169      ChangeRecordEntry entry = null;
170
171      try
172      {
173        entry = reader.readChangeRecord(modifyOptions.getDefaultAdd());
174      } catch (LDIFException le)
175      {
176        logger.traceException(le);
177        if (!modifyOptions.continueOnError())
178        {
179          try
180          {
181            reader.close();
182          }
183          catch (Exception e)
184          {
185            logger.traceException(e);
186          }
187
188          LocalizableMessage message = ERR_LDIF_FILE_INVALID_LDIF_ENTRY.get(
189              le.getLineNumber(), fileNameValue, le);
190          throw new IOException(message.toString());
191        }
192        else
193        {
194          printWrappedText(err, ERR_LDIF_FILE_INVALID_LDIF_ENTRY.get(le.getLineNumber(), fileNameValue, le));
195          continue;
196        }
197      } catch (Exception e)
198      {
199        logger.traceException(e);
200
201        if (!modifyOptions.continueOnError())
202        {
203          try
204          {
205            reader.close();
206          }
207          catch (Exception e2)
208          {
209            logger.traceException(e2);
210          }
211
212          LocalizableMessage message = ERR_LDIF_FILE_READ_ERROR.get(fileNameValue, e);
213          throw new IOException(message.toString());
214        }
215        else
216        {
217          printWrappedText(err, ERR_LDIF_FILE_READ_ERROR.get(fileNameValue, e));
218          continue;
219        }
220      }
221
222      // If the entry is null, then we have reached the end of the config file.
223      if(entry == null)
224      {
225        try
226        {
227          reader.close();
228        }
229        catch (Exception e)
230        {
231          logger.traceException(e);
232        }
233
234        break;
235      }
236
237      ProtocolOp protocolOp = null;
238      ByteString asn1OctetStr =
239          ByteString.valueOfUtf8(entry.getDN().toString());
240
241      String operationType = "";
242      switch(entry.getChangeOperationType())
243      {
244        case ADD:
245          operationType = "ADD";
246          AddChangeRecordEntry addEntry = (AddChangeRecordEntry) entry;
247          List<Attribute> attrs = addEntry.getAttributes();
248          ArrayList<RawAttribute> attributes = new ArrayList<>(attrs.size());
249          for(Attribute a : attrs)
250          {
251            attributes.add(new LDAPAttribute(a));
252          }
253          protocolOp = new AddRequestProtocolOp(asn1OctetStr, attributes);
254          out.println(INFO_PROCESSING_OPERATION.get(operationType, asn1OctetStr));
255          break;
256        case DELETE:
257          operationType = "DELETE";
258          protocolOp = new DeleteRequestProtocolOp(asn1OctetStr);
259          out.println(INFO_PROCESSING_OPERATION.get(operationType, asn1OctetStr));
260          break;
261        case MODIFY:
262          operationType = "MODIFY";
263          ModifyChangeRecordEntry modEntry = (ModifyChangeRecordEntry) entry;
264          ArrayList<RawModification> mods = new ArrayList<>(modEntry.getModifications());
265          protocolOp = new ModifyRequestProtocolOp(asn1OctetStr, mods);
266          out.println(INFO_PROCESSING_OPERATION.get(operationType, asn1OctetStr));
267          break;
268        case MODIFY_DN:
269          operationType = "MODIFY DN";
270          ModifyDNChangeRecordEntry modDNEntry =
271            (ModifyDNChangeRecordEntry) entry;
272          if(modDNEntry.getNewSuperiorDN() != null)
273          {
274            protocolOp = new ModifyDNRequestProtocolOp(asn1OctetStr,
275                ByteString.valueOfUtf8(modDNEntry.getNewRDN().toString()),
276                 modDNEntry.deleteOldRDN(),
277                ByteString.valueOfUtf8(
278                          modDNEntry.getNewSuperiorDN().toString()));
279          } else
280          {
281            protocolOp = new ModifyDNRequestProtocolOp(asn1OctetStr,
282                ByteString.valueOfUtf8(modDNEntry.getNewRDN().toString()),
283                 modDNEntry.deleteOldRDN());
284          }
285
286          out.println(INFO_PROCESSING_OPERATION.get(operationType, asn1OctetStr));
287          break;
288        default:
289          break;
290      }
291
292      if(!modifyOptions.showOperations())
293      {
294        LDAPMessage responseMessage = null;
295        try
296        {
297          LDAPMessage message =
298               new LDAPMessage(nextMessageID.getAndIncrement(), protocolOp,
299                               controls);
300          connection.getLDAPWriter().writeMessage(message);
301          responseMessage = connection.getLDAPReader().readMessage();
302        } catch(DecodeException ae)
303        {
304          logger.traceException(ae);
305          printWrappedText(err, INFO_OPERATION_FAILED.get(operationType));
306          printWrappedText(err, ae.getMessage());
307          if (!modifyOptions.continueOnError())
308          {
309            String msg = LDAPToolUtils.getMessageForConnectionException(ae);
310            throw new IOException(msg, ae);
311          }
312          return;
313        }
314
315        int resultCode = 0;
316        LocalizableMessage errorMessage = null;
317        DN matchedDN = null;
318        List<String> referralURLs = null;
319        try
320        {
321          switch(entry.getChangeOperationType())
322          {
323            case ADD:
324              AddResponseProtocolOp addOp =
325                responseMessage.getAddResponseProtocolOp();
326              resultCode = addOp.getResultCode();
327              errorMessage = addOp.getErrorMessage();
328              matchedDN = addOp.getMatchedDN();
329              referralURLs = addOp.getReferralURLs();
330              break;
331            case DELETE:
332              DeleteResponseProtocolOp delOp =
333                responseMessage.getDeleteResponseProtocolOp();
334              resultCode = delOp.getResultCode();
335              errorMessage = delOp.getErrorMessage();
336              matchedDN = delOp.getMatchedDN();
337              referralURLs = delOp.getReferralURLs();
338              break;
339            case MODIFY:
340              ModifyResponseProtocolOp modOp =
341                responseMessage.getModifyResponseProtocolOp();
342              resultCode = modOp.getResultCode();
343              errorMessage = modOp.getErrorMessage();
344              matchedDN = modOp.getMatchedDN();
345              referralURLs = modOp.getReferralURLs();
346              break;
347            case MODIFY_DN:
348              ModifyDNResponseProtocolOp modDNOp =
349                responseMessage.getModifyDNResponseProtocolOp();
350              resultCode = modDNOp.getResultCode();
351              errorMessage = modDNOp.getErrorMessage();
352              matchedDN = modDNOp.getMatchedDN();
353              referralURLs = modDNOp.getReferralURLs();
354              break;
355            default:
356              break;
357          }
358        }
359        catch (ClassCastException ce)
360        {
361          // It is possible that this is extended response.
362          if (responseMessage.getProtocolOpType() ==
363              LDAPConstants.OP_TYPE_EXTENDED_RESPONSE)
364          {
365            ExtendedResponseProtocolOp extRes =
366              responseMessage.getExtendedResponseProtocolOp();
367            resultCode = extRes.getResultCode();
368            errorMessage = extRes.getErrorMessage();
369            matchedDN = extRes.getMatchedDN();
370            referralURLs = extRes.getReferralURLs();
371          }
372          else
373          {
374            // This should not happen but if it does, then debug log it,
375            // set the error code to OTHER and fall through.
376            logger.traceException(ce);
377            resultCode = ResultCode.OTHER.intValue();
378            errorMessage = null;
379            matchedDN = null;
380            referralURLs = null;
381          }
382        }
383
384        if(resultCode != SUCCESS && resultCode != REFERRAL)
385        {
386          LocalizableMessage msg = INFO_OPERATION_FAILED.get(operationType);
387
388          if(!modifyOptions.continueOnError())
389          {
390            throw new LDAPException(resultCode, errorMessage, msg,
391                                    matchedDN, null);
392          } else
393          {
394            LDAPToolUtils.printErrorMessage(err, msg, resultCode, errorMessage,
395                                            matchedDN);
396          }
397        } else
398        {
399          out.println(INFO_OPERATION_SUCCESSFUL.get(operationType, asn1OctetStr));
400
401          if (errorMessage != null)
402          {
403            printWrappedText(out, errorMessage);
404          }
405
406          if (referralURLs != null)
407          {
408            out.println(referralURLs);
409          }
410        }
411
412
413        for (Control c : responseMessage.getControls())
414        {
415          String oid = c.getOID();
416          if (oid.equals(OID_LDAP_READENTRY_PREREAD))
417          {
418            SearchResultEntry searchEntry;
419            try
420            {
421              LDAPPreReadResponseControl prrc;
422              if(c instanceof LDAPControl)
423              {
424                // Control needs to be decoded
425                prrc = LDAPPreReadResponseControl.DECODER.decode(
426                    c.isCritical(), ((LDAPControl) c).getValue());
427              }
428              else
429              {
430                prrc = (LDAPPreReadResponseControl)c;
431              }
432              searchEntry = prrc.getSearchEntry();
433            }
434            catch (DirectoryException de)
435            {
436              printWrappedText(err, ERR_LDAPMODIFY_PREREAD_CANNOT_DECODE_VALUE.get(de.getMessage()));
437              continue;
438            }
439
440            StringBuilder buffer = new StringBuilder();
441            searchEntry.toString(buffer, 0);
442            out.println(INFO_LDAPMODIFY_PREREAD_ENTRY.get());
443            out.println(buffer);
444          }
445          else if (oid.equals(OID_LDAP_READENTRY_POSTREAD))
446          {
447            SearchResultEntry searchEntry;
448            try
449            {
450              LDAPPostReadResponseControl pprc;
451              if (c instanceof LDAPControl)
452              {
453                // Control needs to be decoded
454                pprc = LDAPPostReadResponseControl.DECODER.decode(c
455                    .isCritical(), ((LDAPControl) c).getValue());
456              }
457              else
458              {
459                pprc = (LDAPPostReadResponseControl)c;
460              }
461              searchEntry = pprc.getSearchEntry();
462            }
463            catch (DirectoryException de)
464            {
465              printWrappedText(err, ERR_LDAPMODIFY_POSTREAD_CANNOT_DECODE_VALUE.get(de.getMessage()));
466              continue;
467            }
468
469            StringBuilder buffer = new StringBuilder();
470            searchEntry.toString(buffer, 0);
471            out.println(INFO_LDAPMODIFY_POSTREAD_ENTRY.get());
472            out.println(buffer);
473          }
474          else if (oid.equals(OID_CSN_CONTROL))
475          {
476            if(c instanceof LDAPControl)
477            {
478              // Don't really need to decode since its just an octet string.
479              out.println(INFO_CHANGE_NUMBER_CONTROL_RESULT.get(
480                  operationType, ((LDAPControl)c).getValue()));
481            }
482            else
483            {
484              out.println(INFO_CHANGE_NUMBER_CONTROL_RESULT.get(operationType,
485                  ((ChangeNumberControlPlugin.ChangeNumberControl)c).getCSN()));
486            }
487          }
488        }
489      }
490    }
491
492  }
493
494  /**
495   * The main method for LDAPModify tool.
496   *
497   * @param  args  The command-line arguments provided to this program.
498   */
499
500  public static void main(String[] args)
501  {
502    int retCode = mainModify(args, true, System.out, System.err);
503
504    if(retCode != 0)
505    {
506      System.exit(filterExitCode(retCode));
507    }
508  }
509
510
511  /**
512   * Parses the provided command-line arguments and uses that information to
513   * run the ldapmodify tool.
514   *
515   * @param  args  The command-line arguments provided to this program.
516   *
517   * @return The error code.
518   */
519
520  public static int mainModify(String[] args)
521  {
522    return mainModify(args, true, System.out, System.err);
523  }
524
525
526  /**
527   * Parses the provided command-line arguments and uses that information to
528   * run the ldapmodify tool.
529   *
530   * @param  args              The command-line arguments provided to this
531   *                           program.
532   * @param  initializeServer  Indicates whether to initialize the server.
533   * @param  outStream         The output stream to use for standard output, or
534   *                           <CODE>null</CODE> if standard output is not
535   *                           needed.
536   * @param  errStream         The output stream to use for standard error, or
537   *                           <CODE>null</CODE> if standard error is not
538   *                           needed.
539   *
540   * @return The error code.
541   */
542
543  public static int mainModify(String[] args, boolean initializeServer,
544                               OutputStream outStream, OutputStream errStream)
545  {
546    PrintStream out = NullOutputStream.wrapOrNullStream(outStream);
547    PrintStream err = NullOutputStream.wrapOrNullStream(errStream);
548
549    LDAPConnectionOptions connectionOptions = new LDAPConnectionOptions();
550    LDAPModifyOptions modifyOptions = new LDAPModifyOptions();
551    LDAPConnection connection = null;
552
553    final BooleanArgument continueOnError;
554    final BooleanArgument defaultAdd;
555    final BooleanArgument noop;
556    final BooleanArgument reportAuthzID;
557    final BooleanArgument saslExternal;
558    final BooleanArgument showUsage;
559    final BooleanArgument startTLS;
560    final BooleanArgument trustAll;
561    final BooleanArgument useSSL;
562    final BooleanArgument verbose;
563    final FileBasedArgument bindPasswordFile;
564    final FileBasedArgument keyStorePasswordFile;
565    final FileBasedArgument trustStorePasswordFile;
566    final IntegerArgument connectTimeout;
567    final IntegerArgument port;
568    final IntegerArgument version;
569    final StringArgument assertionFilter;
570    final StringArgument bindDN;
571    final StringArgument bindPassword;
572    final StringArgument certNickname;
573    final StringArgument controlStr;
574    final StringArgument encodingStr;
575    final StringArgument filename;
576    final StringArgument hostName;
577    final StringArgument keyStorePath;
578    final StringArgument keyStorePassword;
579    final StringArgument postReadAttributes;
580    final StringArgument preReadAttributes;
581    final StringArgument proxyAuthzID;
582    final StringArgument saslOptions;
583    final StringArgument trustStorePath;
584    final StringArgument trustStorePassword;
585    final StringArgument propertiesFileArgument;
586    final BooleanArgument noPropertiesFileArgument;
587
588    // Create the command-line argument parser for use with this program.
589    LocalizableMessage toolDescription = INFO_LDAPMODIFY_TOOL_DESCRIPTION.get();
590    ArgumentParser argParser = new ArgumentParser(CLASS_NAME, toolDescription,
591                                                  false);
592    argParser.setShortToolDescription(REF_SHORT_DESC_LDAPMODIFY.get());
593    argParser.setVersionHandler(new DirectoryServerVersionHandler());
594    try
595    {
596      propertiesFileArgument =
597              StringArgument.builder(OPTION_LONG_PROP_FILE_PATH)
598                      .description(INFO_DESCRIPTION_PROP_FILE_PATH.get())
599                      .valuePlaceholder(INFO_PROP_FILE_PATH_PLACEHOLDER.get())
600                      .buildAndAddToParser(argParser);
601      argParser.setFilePropertiesArgument(propertiesFileArgument);
602
603      noPropertiesFileArgument =
604              BooleanArgument.builder(OPTION_LONG_NO_PROP_FILE)
605                      .description(INFO_DESCRIPTION_NO_PROP_FILE.get())
606                      .buildAndAddToParser(argParser);
607      argParser.setNoPropertiesFileArgument(noPropertiesFileArgument);
608
609      hostName =
610              StringArgument.builder(OPTION_LONG_HOST)
611                      .shortIdentifier(OPTION_SHORT_HOST)
612                      .description(INFO_DESCRIPTION_HOST.get())
613                      .defaultValue("localhost")
614                      .valuePlaceholder(INFO_HOST_PLACEHOLDER.get())
615                      .buildAndAddToParser(argParser);
616      port =
617              IntegerArgument.builder(OPTION_LONG_PORT)
618                      .shortIdentifier(OPTION_SHORT_PORT)
619                      .description(INFO_DESCRIPTION_PORT.get())
620                      .range(1, 65535)
621                      .defaultValue(389)
622                      .valuePlaceholder(INFO_PORT_PLACEHOLDER.get())
623                      .buildAndAddToParser(argParser);
624      useSSL =
625              BooleanArgument.builder(OPTION_LONG_USE_SSL)
626                      .shortIdentifier(OPTION_SHORT_USE_SSL)
627                      .description(INFO_DESCRIPTION_USE_SSL.get())
628                      .buildAndAddToParser(argParser);
629      startTLS =
630              BooleanArgument.builder(OPTION_LONG_START_TLS)
631                      .shortIdentifier(OPTION_SHORT_START_TLS)
632                      .description(INFO_DESCRIPTION_START_TLS.get())
633                      .buildAndAddToParser(argParser);
634      bindDN =
635              StringArgument.builder(OPTION_LONG_BINDDN)
636                      .shortIdentifier(OPTION_SHORT_BINDDN)
637                      .description(INFO_DESCRIPTION_BINDDN.get())
638                      .valuePlaceholder(INFO_BINDDN_PLACEHOLDER.get())
639                      .buildAndAddToParser(argParser);
640      bindPassword =
641              StringArgument.builder(OPTION_LONG_BINDPWD)
642                      .shortIdentifier(OPTION_SHORT_BINDPWD)
643                      .description(INFO_DESCRIPTION_BINDPASSWORD.get())
644                      .valuePlaceholder(INFO_BINDPWD_PLACEHOLDER.get())
645                      .buildAndAddToParser(argParser);
646      bindPasswordFile =
647              FileBasedArgument.builder(OPTION_LONG_BINDPWD_FILE)
648                      .shortIdentifier(OPTION_SHORT_BINDPWD_FILE)
649                      .description(INFO_DESCRIPTION_BINDPASSWORDFILE.get())
650                      .valuePlaceholder(INFO_BINDPWD_FILE_PLACEHOLDER.get())
651                      .buildAndAddToParser(argParser);
652      defaultAdd =
653              BooleanArgument.builder("defaultAdd")
654                      .shortIdentifier('a')
655                      .description(INFO_MODIFY_DESCRIPTION_DEFAULT_ADD.get())
656                      .buildAndAddToParser(argParser);
657      filename =
658              StringArgument.builder(OPTION_LONG_FILENAME)
659                      .shortIdentifier(OPTION_SHORT_FILENAME)
660                      .description(INFO_LDAPMODIFY_DESCRIPTION_FILENAME.get())
661                      .valuePlaceholder(INFO_FILE_PLACEHOLDER.get())
662                      .buildAndAddToParser(argParser);
663      saslExternal =
664              BooleanArgument.builder("useSASLExternal")
665                      .shortIdentifier('r')
666                      .description(INFO_DESCRIPTION_USE_SASL_EXTERNAL.get())
667                      .buildAndAddToParser(argParser);
668      saslOptions =
669              StringArgument.builder(OPTION_LONG_SASLOPTION)
670                      .shortIdentifier(OPTION_SHORT_SASLOPTION)
671                      .description(INFO_DESCRIPTION_SASL_PROPERTIES.get())
672                      .multiValued()
673                      .valuePlaceholder(INFO_SASL_OPTION_PLACEHOLDER.get())
674                      .buildAndAddToParser(argParser);
675
676      trustAll = trustAllArgument();
677      argParser.addArgument(trustAll);
678
679      keyStorePath =
680              StringArgument.builder(OPTION_LONG_KEYSTOREPATH)
681                      .shortIdentifier(OPTION_SHORT_KEYSTOREPATH)
682                      .description(INFO_DESCRIPTION_KEYSTOREPATH.get())
683                      .valuePlaceholder(INFO_KEYSTOREPATH_PLACEHOLDER.get())
684                      .buildAndAddToParser(argParser);
685      keyStorePassword =
686              StringArgument.builder(OPTION_LONG_KEYSTORE_PWD)
687                      .shortIdentifier(OPTION_SHORT_KEYSTORE_PWD)
688                      .description(INFO_DESCRIPTION_KEYSTOREPASSWORD.get())
689                      .valuePlaceholder(INFO_KEYSTORE_PWD_PLACEHOLDER.get())
690                      .buildAndAddToParser(argParser);
691      keyStorePasswordFile =
692              FileBasedArgument.builder(OPTION_LONG_KEYSTORE_PWD_FILE)
693                      .shortIdentifier(OPTION_SHORT_KEYSTORE_PWD_FILE)
694                      .description(INFO_DESCRIPTION_KEYSTOREPASSWORD_FILE.get())
695                      .valuePlaceholder(INFO_KEYSTORE_PWD_FILE_PLACEHOLDER.get())
696                      .buildAndAddToParser(argParser);
697      certNickname =
698              StringArgument.builder("certNickname")
699                      .shortIdentifier('N')
700                      .description(INFO_DESCRIPTION_CERT_NICKNAME.get())
701                      .valuePlaceholder(INFO_NICKNAME_PLACEHOLDER.get())
702                      .buildAndAddToParser(argParser);
703      trustStorePath =
704              StringArgument.builder(OPTION_LONG_TRUSTSTOREPATH)
705                      .shortIdentifier(OPTION_SHORT_TRUSTSTOREPATH)
706                      .description(INFO_DESCRIPTION_TRUSTSTOREPATH.get())
707                      .valuePlaceholder(INFO_TRUSTSTOREPATH_PLACEHOLDER.get())
708                      .buildAndAddToParser(argParser);
709      trustStorePassword =
710              StringArgument.builder(OPTION_LONG_TRUSTSTORE_PWD)
711                      .description(INFO_DESCRIPTION_TRUSTSTOREPASSWORD.get())
712                      .valuePlaceholder(INFO_TRUSTSTORE_PWD_PLACEHOLDER.get())
713                      .buildAndAddToParser(argParser);
714      trustStorePasswordFile =
715              FileBasedArgument.builder(OPTION_LONG_TRUSTSTORE_PWD_FILE)
716                      .shortIdentifier(OPTION_SHORT_TRUSTSTORE_PWD_FILE)
717                      .description(INFO_DESCRIPTION_TRUSTSTOREPASSWORD_FILE.get())
718                      .valuePlaceholder(INFO_TRUSTSTORE_PWD_FILE_PLACEHOLDER.get())
719                      .buildAndAddToParser(argParser);
720      proxyAuthzID =
721              StringArgument.builder(OPTION_LONG_PROXYAUTHID)
722                      .shortIdentifier(OPTION_SHORT_PROXYAUTHID)
723                      .description(INFO_DESCRIPTION_PROXY_AUTHZID.get())
724                      .valuePlaceholder(INFO_PROXYAUTHID_PLACEHOLDER.get())
725                      .buildAndAddToParser(argParser);
726      reportAuthzID =
727              BooleanArgument.builder("reportAuthzID")
728                      .shortIdentifier('E')
729                      .description(INFO_DESCRIPTION_REPORT_AUTHZID.get())
730                      .buildAndAddToParser(argParser);
731      assertionFilter =
732              StringArgument.builder(OPTION_LONG_ASSERTION_FILE)
733                      .description(INFO_DESCRIPTION_ASSERTION_FILTER.get())
734                      .valuePlaceholder(INFO_ASSERTION_FILTER_PLACEHOLDER.get())
735                      .buildAndAddToParser(argParser);
736      preReadAttributes =
737              StringArgument.builder("preReadAttributes")
738                      .description(INFO_DESCRIPTION_PREREAD_ATTRS.get())
739                      .valuePlaceholder(INFO_ATTRIBUTE_LIST_PLACEHOLDER.get())
740                      .buildAndAddToParser(argParser);
741      postReadAttributes =
742              StringArgument.builder("postReadAttributes")
743                      .description(INFO_DESCRIPTION_POSTREAD_ATTRS.get())
744                      .valuePlaceholder(INFO_ATTRIBUTE_LIST_PLACEHOLDER.get())
745                      .buildAndAddToParser(argParser);
746      controlStr =
747              StringArgument.builder("control")
748                      .shortIdentifier('J')
749                      .description(INFO_DESCRIPTION_CONTROLS.get())
750                      .multiValued()
751                      .valuePlaceholder(INFO_LDAP_CONTROL_PLACEHOLDER.get())
752                      .buildAndAddToParser(argParser);
753      version =
754              IntegerArgument.builder(OPTION_LONG_PROTOCOL_VERSION)
755                      .shortIdentifier(OPTION_SHORT_PROTOCOL_VERSION)
756                      .description(INFO_DESCRIPTION_VERSION.get())
757                      .defaultValue(3)
758                      .valuePlaceholder(INFO_PROTOCOL_VERSION_PLACEHOLDER.get())
759                      .buildAndAddToParser(argParser);
760        connectTimeout =
761                IntegerArgument.builder(OPTION_LONG_CONNECT_TIMEOUT)
762                        .description(INFO_DESCRIPTION_CONNECTION_TIMEOUT.get())
763                        .lowerBound(0)
764                        .defaultValue(CliConstants.DEFAULT_LDAP_CONNECT_TIMEOUT)
765                        .valuePlaceholder(INFO_TIMEOUT_PLACEHOLDER.get())
766                        .buildAndAddToParser(argParser);
767      encodingStr =
768              StringArgument.builder("encoding")
769                      .shortIdentifier('i')
770                      .description(INFO_DESCRIPTION_ENCODING.get())
771                      .valuePlaceholder(INFO_ENCODING_PLACEHOLDER.get())
772                      .buildAndAddToParser(argParser);
773      continueOnError =
774              BooleanArgument.builder("continueOnError")
775                      .shortIdentifier('c')
776                      .description(INFO_DESCRIPTION_CONTINUE_ON_ERROR.get())
777                      .buildAndAddToParser(argParser);
778      noop =
779              BooleanArgument.builder(OPTION_LONG_DRYRUN)
780                      .shortIdentifier(OPTION_SHORT_DRYRUN)
781                      .description(INFO_DESCRIPTION_NOOP.get())
782                      .buildAndAddToParser(argParser);
783
784      verbose = verboseArgument();
785      argParser.addArgument(verbose);
786
787      showUsage = showUsageArgument();
788      argParser.addArgument(showUsage);
789      argParser.setUsageArgument(showUsage, out);
790    } catch (ArgumentException ae)
791    {
792      printWrappedText(err, ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage()));
793      return CLIENT_SIDE_PARAM_ERROR;
794    }
795
796    // Parse the command-line arguments provided to this program.
797    try
798    {
799      argParser.parseArguments(args);
800    }
801    catch (ArgumentException ae)
802    {
803      argParser.displayMessageAndUsageReference(err, ERR_ERROR_PARSING_ARGS.get(ae.getMessage()));
804      return CLIENT_SIDE_PARAM_ERROR;
805    }
806
807    // If we should just display usage or version information,
808    // then print it and exit.
809    if (argParser.usageOrVersionDisplayed())
810    {
811      return SUCCESS;
812    }
813
814    if (bindPassword.isPresent() && bindPasswordFile.isPresent())
815    {
816      printWrappedText(err, conflictingArgsErrorMessage(bindPassword, bindPasswordFile));
817      return CLIENT_SIDE_PARAM_ERROR;
818    }
819
820    String hostNameValue = hostName.getValue();
821    int portNumber = 389;
822    try
823    {
824      portNumber = port.getIntValue();
825    } catch(ArgumentException ae)
826    {
827      logger.traceException(ae);
828      argParser.displayMessageAndUsageReference(err, ae.getMessageObject());
829      return CLIENT_SIDE_PARAM_ERROR;
830    }
831
832    try
833    {
834      int versionNumber = version.getIntValue();
835      if(versionNumber != 2 && versionNumber != 3)
836      {
837        printWrappedText(err, ERR_DESCRIPTION_INVALID_VERSION.get(versionNumber));
838        return CLIENT_SIDE_PARAM_ERROR;
839      }
840      connectionOptions.setVersionNumber(versionNumber);
841    } catch(ArgumentException ae)
842    {
843      logger.traceException(ae);
844      argParser.displayMessageAndUsageReference(err, ae.getMessageObject());
845      return CLIENT_SIDE_PARAM_ERROR;
846    }
847
848    String bindDNValue = bindDN.getValue();
849    String fileNameValue = filename.getValue();
850    String bindPasswordValue;
851    try
852    {
853      bindPasswordValue = getPasswordValue(
854          bindPassword, bindPasswordFile, bindDNValue, out, err);
855    }
856    catch (Exception ex)
857    {
858      logger.traceException(ex);
859      printWrappedText(err, ex.getMessage());
860      return CLIENT_SIDE_PARAM_ERROR;
861    }
862
863    String keyStorePathValue = keyStorePath.getValue();
864    String trustStorePathValue = trustStorePath.getValue();
865
866    String keyStorePasswordValue = null;
867    if (keyStorePassword.isPresent())
868    {
869      keyStorePasswordValue = keyStorePassword.getValue();
870    }
871    else if (keyStorePasswordFile.isPresent())
872    {
873      keyStorePasswordValue = keyStorePasswordFile.getValue();
874    }
875
876    String trustStorePasswordValue = null;
877    if (trustStorePassword.isPresent())
878    {
879      trustStorePasswordValue = trustStorePassword.getValue();
880    }
881    else if (trustStorePasswordFile.isPresent())
882    {
883      trustStorePasswordValue = trustStorePasswordFile.getValue();
884    }
885
886    modifyOptions.setShowOperations(noop.isPresent());
887    modifyOptions.setVerbose(verbose.isPresent());
888    modifyOptions.setContinueOnError(continueOnError.isPresent());
889    modifyOptions.setEncoding(encodingStr.getValue());
890    modifyOptions.setDefaultAdd(defaultAdd.isPresent());
891
892    if (controlStr.isPresent())
893    {
894      for (String ctrlString : controlStr.getValues())
895      {
896        Control ctrl = LDAPToolUtils.getControl(ctrlString, err);
897        if(ctrl == null)
898        {
899          printWrappedText(err, ERR_TOOL_INVALID_CONTROL_STRING.get(ctrlString));
900          return CLIENT_SIDE_PARAM_ERROR;
901        }
902        modifyOptions.getControls().add(ctrl);
903      }
904    }
905
906    if (proxyAuthzID.isPresent())
907    {
908      Control proxyControl =
909          new ProxiedAuthV2Control(true,
910              ByteString.valueOfUtf8(proxyAuthzID.getValue()));
911      modifyOptions.getControls().add(proxyControl);
912    }
913
914    if (assertionFilter.isPresent())
915    {
916      String filterString = assertionFilter.getValue();
917      LDAPFilter filter;
918      try
919      {
920        filter = LDAPFilter.decode(filterString);
921
922        Control assertionControl =
923            new LDAPAssertionRequestControl(true, filter);
924        modifyOptions.getControls().add(assertionControl);
925      }
926      catch (LDAPException le)
927      {
928        printWrappedText(err, ERR_LDAP_ASSERTION_INVALID_FILTER.get(le.getMessage()));
929        return CLIENT_SIDE_PARAM_ERROR;
930      }
931    }
932
933    if (preReadAttributes.isPresent())
934    {
935      String valueStr = preReadAttributes.getValue();
936      Set<String> attrElements = new LinkedHashSet<>();
937
938      StringTokenizer tokenizer = new StringTokenizer(valueStr, ", ");
939      while (tokenizer.hasMoreTokens())
940      {
941        attrElements.add(tokenizer.nextToken());
942      }
943
944      Control c = new LDAPPreReadRequestControl(true, attrElements);
945      modifyOptions.getControls().add(c);
946    }
947
948    if (postReadAttributes.isPresent())
949    {
950      String valueStr = postReadAttributes.getValue();
951      Set<String> attrElements = new LinkedHashSet<>();
952
953      StringTokenizer tokenizer = new StringTokenizer(valueStr, ", ");
954      while (tokenizer.hasMoreTokens())
955      {
956        attrElements.add(tokenizer.nextToken());
957      }
958
959      Control c = new LDAPPostReadRequestControl(true, attrElements);
960      modifyOptions.getControls().add(c);
961    }
962
963    // Set the connection options.
964    connectionOptions.setSASLExternal(saslExternal.isPresent());
965    if(saslOptions.isPresent())
966    {
967      for (String saslOption : saslOptions.getValues())
968      {
969        boolean val = saslOption.startsWith("mech=")
970            ? connectionOptions.setSASLMechanism(saslOption)
971            : connectionOptions.addSASLProperty(saslOption);
972        if (!val)
973        {
974          return CLIENT_SIDE_PARAM_ERROR;
975        }
976      }
977    }
978
979    connectionOptions.setUseSSL(useSSL.isPresent());
980    connectionOptions.setStartTLS(startTLS.isPresent());
981    connectionOptions.setReportAuthzID(reportAuthzID.isPresent());
982
983    if(connectionOptions.useSASLExternal())
984    {
985      if(!connectionOptions.useSSL() && !connectionOptions.useStartTLS())
986      {
987        printWrappedText(err, ERR_TOOL_SASLEXTERNAL_NEEDS_SSL_OR_TLS.get());
988        return CLIENT_SIDE_PARAM_ERROR;
989      }
990      if(keyStorePathValue == null)
991      {
992        printWrappedText(err, ERR_TOOL_SASLEXTERNAL_NEEDS_KEYSTORE.get());
993        return CLIENT_SIDE_PARAM_ERROR;
994      }
995    }
996
997    connectionOptions.setVerbose(verbose.isPresent());
998
999    LDAPModify ldapModify = null;
1000    try
1001    {
1002      if (initializeServer)
1003      {
1004        // Bootstrap and initialize directory data structures.
1005        EmbeddedUtils.initializeForClientUse();
1006      }
1007
1008      // Connect to the specified host with the supplied userDN and password.
1009      SSLConnectionFactory sslConnectionFactory = null;
1010      if(connectionOptions.useSSL() || connectionOptions.useStartTLS())
1011      {
1012        String clientAlias;
1013        if (certNickname.isPresent())
1014        {
1015          clientAlias = certNickname.getValue();
1016        }
1017        else
1018        {
1019          clientAlias = null;
1020        }
1021
1022        sslConnectionFactory = new SSLConnectionFactory();
1023        sslConnectionFactory.init(trustAll.isPresent(), keyStorePathValue,
1024                                  keyStorePasswordValue, clientAlias,
1025                                  trustStorePathValue, trustStorePasswordValue);
1026        connectionOptions.setSSLConnectionFactory(sslConnectionFactory);
1027      }
1028
1029      AtomicInteger nextMessageID = new AtomicInteger(1);
1030      connection = new LDAPConnection(hostNameValue, portNumber,
1031                                      connectionOptions, out, err);
1032      int timeout = connectTimeout.getIntValue();
1033      connection.connectToHost(bindDNValue, bindPasswordValue, nextMessageID,
1034          timeout);
1035
1036      ldapModify = new LDAPModify(nextMessageID, out, err);
1037      ldapModify.readAndExecute(connection, fileNameValue, modifyOptions);
1038    } catch(LDAPException le)
1039    {
1040      logger.traceException(le);
1041      LDAPToolUtils.printErrorMessage(err, le.getMessageObject(),
1042                                      le.getResultCode(),
1043                                      le.getErrorMessage(), le.getMatchedDN());
1044      return le.getResultCode();
1045    } catch(LDAPConnectionException lce)
1046    {
1047      logger.traceException(lce);
1048      LDAPToolUtils.printErrorMessage(err, lce.getMessageObject(),
1049                                      lce.getResultCode(),
1050                                      lce.getErrorMessage(),
1051                                      lce.getMatchedDN());
1052      return lce.getResultCode();
1053    } catch (FileNotFoundException fe)
1054    {
1055      logger.traceException(fe);
1056      printWrappedText(err, fe.getMessage());
1057      return CLIENT_SIDE_PARAM_ERROR;
1058    }
1059    catch(ArgumentException e)
1060    {
1061      argParser.displayMessageAndUsageReference(err, e.getMessageObject());
1062      return 1;
1063    }
1064    catch(Exception e)
1065    {
1066      logger.traceException(e);
1067      printWrappedText(err, e.getMessage());
1068      return OPERATIONS_ERROR;
1069    } finally
1070    {
1071      if(connection != null)
1072      {
1073        if (ldapModify == null)
1074        {
1075          connection.close(null);
1076        }
1077        else
1078        {
1079          connection.close(ldapModify.nextMessageID);
1080        }
1081      }
1082    }
1083    return SUCCESS;
1084  }
1085
1086}
1087