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