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 2013-2016 ForgeRock AS.
016 */
017package org.opends.server.tools;
018
019import static org.opends.messages.ToolMessages.*;
020import static org.opends.server.extensions.ExtensionsConstants.*;
021import static org.opends.server.protocols.ldap.LDAPResultCode.*;
022import static org.opends.server.util.ServerConstants.*;
023import static org.opends.server.util.StaticUtils.*;
024
025import static com.forgerock.opendj.cli.ArgumentConstants.*;
026import static com.forgerock.opendj.cli.Utils.*;
027import static com.forgerock.opendj.cli.CommonArguments.*;
028
029import java.io.OutputStream;
030import java.io.PrintStream;
031import java.util.ArrayList;
032import java.util.concurrent.atomic.AtomicInteger;
033
034import org.forgerock.i18n.LocalizableMessage;
035import org.forgerock.opendj.io.ASN1;
036import org.forgerock.opendj.io.ASN1Reader;
037import org.forgerock.opendj.io.ASN1Writer;
038import org.forgerock.opendj.ldap.ByteString;
039import org.forgerock.opendj.ldap.ByteStringBuilder;
040import org.opends.server.controls.PasswordPolicyErrorType;
041import org.opends.server.controls.PasswordPolicyResponseControl;
042import org.opends.server.controls.PasswordPolicyWarningType;
043import org.opends.server.core.DirectoryServer.DirectoryServerVersionHandler;
044import org.opends.server.protocols.ldap.ExtendedRequestProtocolOp;
045import org.opends.server.protocols.ldap.ExtendedResponseProtocolOp;
046import org.opends.server.protocols.ldap.LDAPControl;
047import org.opends.server.protocols.ldap.LDAPMessage;
048import org.opends.server.protocols.ldap.LDAPResultCode;
049import org.opends.server.protocols.ldap.UnbindRequestProtocolOp;
050import org.opends.server.types.Control;
051import org.forgerock.opendj.ldap.DN;
052import org.opends.server.types.NullOutputStream;
053import org.opends.server.util.EmbeddedUtils;
054
055import com.forgerock.opendj.cli.ArgumentException;
056import com.forgerock.opendj.cli.ArgumentParser;
057import com.forgerock.opendj.cli.BooleanArgument;
058import com.forgerock.opendj.cli.CliConstants;
059import com.forgerock.opendj.cli.ConsoleApplication;
060import com.forgerock.opendj.cli.FileBasedArgument;
061import com.forgerock.opendj.cli.IntegerArgument;
062import com.forgerock.opendj.cli.StringArgument;
063
064/**
065 * This program provides a utility that uses the LDAP password modify extended
066 * operation to change the password for a user.  It exposes the three primary
067 * options available for this operation, which are:
068 *
069 * <UL>
070 *   <LI>The user identity whose password should be changed.</LI>
071 *   <LI>The current password for the user.</LI>
072 *   <LI>The new password for the user.</LI>
073 * </UL>
074 *
075 * All of these are optional components that may be included or omitted from the
076 * request.
077 */
078public class LDAPPasswordModify
079{
080  /** The fully-qualified name of this class. */
081  private static final String CLASS_NAME =
082       "org.opends.server.tools.LDAPPasswordModify";
083
084  /**
085   * Parses the command-line arguments, establishes a connection to the
086   * Directory Server, sends the password modify request, and reads the
087   * response.
088   *
089   * @param  args  The command-line arguments provided to this program.
090   */
091  public static void main(String[] args)
092  {
093    int returnCode = mainPasswordModify(args, true, System.out, System.err);
094    if (returnCode != 0)
095    {
096      System.exit(filterExitCode(returnCode));
097    }
098  }
099
100  /**
101   * Parses the command-line arguments, establishes a connection to the
102   * Directory Server, sends the password modify request, and reads the
103   * response.
104   *
105   * @param  args              The command-line arguments provided to this
106   *                           program.
107   * @param  initializeServer  Indicates whether to initialize the server.
108   * @param  outStream         The output stream to use for standard output.
109   * @param  errStream         The output stream to use for standard error.
110   *
111   * @return  An integer value of zero if everything completed successfully, or
112   *          a nonzero value if an error occurred.
113   */
114  public static int mainPasswordModify(String[] args, boolean initializeServer,
115                                       OutputStream outStream,
116                                       OutputStream errStream)
117  {
118    PrintStream out = NullOutputStream.wrapOrNullStream(outStream);
119    PrintStream err = NullOutputStream.wrapOrNullStream(errStream);
120
121    // Create the arguments that will be used by this program.
122    BooleanArgument   provideDNForAuthzID;
123    BooleanArgument   showUsage;
124    BooleanArgument   trustAll;
125    BooleanArgument   useSSL;
126    BooleanArgument   useStartTLS;
127    FileBasedArgument bindPWFile;
128    StringArgument    certNickname;
129    FileBasedArgument currentPWFile;
130    FileBasedArgument newPWFile;
131    FileBasedArgument sslKeyStorePINFile;
132    FileBasedArgument sslTrustStorePINFile;
133    IntegerArgument   ldapPort;
134    StringArgument    authzID;
135    StringArgument    bindDN;
136    StringArgument    bindPW;
137    StringArgument    controlStr;
138    StringArgument    currentPW;
139    StringArgument    ldapHost;
140    StringArgument    newPW;
141    StringArgument    sslKeyStore;
142    StringArgument    sslKeyStorePIN;
143    StringArgument    sslTrustStore;
144    StringArgument    sslTrustStorePIN;
145    IntegerArgument   connectTimeout;
146    StringArgument    propertiesFileArgument;
147    BooleanArgument   noPropertiesFileArgument;
148
149    // Initialize the argument parser.
150    LocalizableMessage toolDescription = INFO_LDAPPWMOD_TOOL_DESCRIPTION.get();
151    ArgumentParser argParser = new ArgumentParser(CLASS_NAME, toolDescription,
152                                                  false);
153    argParser.setShortToolDescription(REF_SHORT_DESC_LDAPPASSWORDMODIFY.get());
154    argParser.setVersionHandler(new DirectoryServerVersionHandler());
155
156    try
157    {
158      propertiesFileArgument =
159              StringArgument.builder(OPTION_LONG_PROP_FILE_PATH)
160                      .description(INFO_DESCRIPTION_PROP_FILE_PATH.get())
161                      .valuePlaceholder(INFO_PROP_FILE_PATH_PLACEHOLDER.get())
162                      .buildAndAddToParser(argParser);
163      argParser.setFilePropertiesArgument(propertiesFileArgument);
164
165      noPropertiesFileArgument =
166              BooleanArgument.builder(OPTION_LONG_NO_PROP_FILE)
167                      .description(INFO_DESCRIPTION_NO_PROP_FILE.get())
168                      .buildAndAddToParser(argParser);
169      argParser.setNoPropertiesFileArgument(noPropertiesFileArgument);
170
171      ldapHost =
172              StringArgument.builder(OPTION_LONG_HOST)
173                      .shortIdentifier(OPTION_SHORT_HOST)
174                      .description(INFO_LDAPPWMOD_DESCRIPTION_HOST.get())
175                      .defaultValue("127.0.0.1")
176                      .valuePlaceholder(INFO_HOST_PLACEHOLDER.get())
177                      .buildAndAddToParser(argParser);
178      ldapPort =
179              IntegerArgument.builder(OPTION_LONG_PORT)
180                      .shortIdentifier(OPTION_SHORT_PORT)
181                      .description(INFO_LDAPPWMOD_DESCRIPTION_PORT.get())
182                      .range(1, 65535)
183                      .defaultValue(389)
184                      .valuePlaceholder(INFO_PORT_PLACEHOLDER.get())
185                      .buildAndAddToParser(argParser);
186      useSSL =
187              BooleanArgument.builder(OPTION_LONG_USE_SSL)
188                      .shortIdentifier(OPTION_SHORT_USE_SSL)
189                      .description(INFO_LDAPPWMOD_DESCRIPTION_USE_SSL.get())
190                      .buildAndAddToParser(argParser);
191      useStartTLS =
192              BooleanArgument.builder(OPTION_LONG_START_TLS)
193                      .shortIdentifier(OPTION_SHORT_START_TLS)
194                      .description(INFO_LDAPPWMOD_DESCRIPTION_USE_STARTTLS.get())
195                      .buildAndAddToParser(argParser);
196      bindDN =
197              StringArgument.builder(OPTION_LONG_BINDDN)
198                      .shortIdentifier(OPTION_SHORT_BINDDN)
199                      .description(INFO_LDAPPWMOD_DESCRIPTION_BIND_DN.get())
200                      .valuePlaceholder(INFO_BINDDN_PLACEHOLDER.get())
201                      .buildAndAddToParser(argParser);
202      bindPW =
203              StringArgument.builder(OPTION_LONG_BINDPWD)
204                      .shortIdentifier(OPTION_SHORT_BINDPWD)
205                      .description(INFO_LDAPPWMOD_DESCRIPTION_BIND_PW.get())
206                      .valuePlaceholder(INFO_BINDPWD_PLACEHOLDER.get())
207                      .buildAndAddToParser(argParser);
208      bindPWFile =
209              FileBasedArgument.builder(OPTION_LONG_BINDPWD_FILE)
210                      .shortIdentifier(OPTION_SHORT_BINDPWD_FILE)
211                      .description(INFO_LDAPPWMOD_DESCRIPTION_BIND_PW_FILE.get())
212                      .valuePlaceholder(INFO_BINDPWD_FILE_PLACEHOLDER.get())
213                      .buildAndAddToParser(argParser);
214      authzID =
215              StringArgument.builder("authzID")
216                      .shortIdentifier('a')
217                      .description(INFO_LDAPPWMOD_DESCRIPTION_AUTHZID.get())
218                      .valuePlaceholder(INFO_PROXYAUTHID_PLACEHOLDER.get())
219                      .buildAndAddToParser(argParser);
220      provideDNForAuthzID =
221              BooleanArgument.builder("provideDNForAuthzID")
222                      .shortIdentifier('A')
223                      .description(INFO_LDAPPWMOD_DESCRIPTION_PROVIDE_DN_FOR_AUTHZID.get())
224                      .buildAndAddToParser(argParser);
225      newPW =
226              StringArgument.builder("newPassword")
227                      .shortIdentifier('n')
228                      .description(INFO_LDAPPWMOD_DESCRIPTION_NEWPW.get())
229                      .valuePlaceholder(INFO_NEW_PASSWORD_PLACEHOLDER.get())
230                      .buildAndAddToParser(argParser);
231      newPWFile =
232              FileBasedArgument.builder("newPasswordFile")
233                      .shortIdentifier('N')
234                      .description(INFO_LDAPPWMOD_DESCRIPTION_NEWPWFILE.get())
235                      .valuePlaceholder(INFO_FILE_PLACEHOLDER.get())
236                      .buildAndAddToParser(argParser);
237      currentPW =
238              StringArgument.builder("currentPassword")
239                      .shortIdentifier('c')
240                      .description(INFO_LDAPPWMOD_DESCRIPTION_CURRENTPW.get())
241                      .valuePlaceholder(INFO_CURRENT_PASSWORD_PLACEHOLDER.get())
242                      .buildAndAddToParser(argParser);
243      currentPWFile =
244              FileBasedArgument.builder("currentPasswordFile")
245                      .shortIdentifier('C')
246                      .description(INFO_LDAPPWMOD_DESCRIPTION_CURRENTPWFILE.get())
247                      .valuePlaceholder(INFO_FILE_PLACEHOLDER.get())
248                      .buildAndAddToParser(argParser);
249
250      trustAll = trustAllArgument();
251      argParser.addArgument(trustAll);
252
253      sslKeyStore =
254              StringArgument.builder(OPTION_LONG_KEYSTOREPATH)
255                      .shortIdentifier(OPTION_SHORT_KEYSTOREPATH)
256                      .description(INFO_LDAPPWMOD_DESCRIPTION_KEYSTORE.get())
257                      .valuePlaceholder(INFO_KEYSTOREPATH_PLACEHOLDER.get())
258                      .buildAndAddToParser(argParser);
259      sslKeyStorePIN =
260              StringArgument.builder(OPTION_LONG_KEYSTORE_PWD)
261                      .shortIdentifier(OPTION_SHORT_KEYSTORE_PWD)
262                      .description(INFO_LDAPPWMOD_DESCRIPTION_KEYSTORE_PIN.get())
263                      .valuePlaceholder(INFO_KEYSTORE_PWD_PLACEHOLDER.get())
264                      .buildAndAddToParser(argParser);
265      sslKeyStorePINFile =
266              FileBasedArgument.builder(OPTION_LONG_KEYSTORE_PWD_FILE)
267                      .shortIdentifier(OPTION_SHORT_KEYSTORE_PWD_FILE)
268                      .description(INFO_LDAPPWMOD_DESCRIPTION_KEYSTORE_PINFILE.get())
269                      .valuePlaceholder(INFO_KEYSTORE_PWD_FILE_PLACEHOLDER.get())
270                      .buildAndAddToParser(argParser);
271      certNickname =
272              StringArgument.builder("certNickname")
273                      .description(INFO_DESCRIPTION_CERT_NICKNAME.get())
274                      .valuePlaceholder(INFO_NICKNAME_PLACEHOLDER.get())
275                      .buildAndAddToParser(argParser);
276      sslTrustStore =
277              StringArgument.builder(OPTION_LONG_TRUSTSTOREPATH)
278                      .shortIdentifier(OPTION_SHORT_TRUSTSTOREPATH)
279                      .description(INFO_LDAPPWMOD_DESCRIPTION_TRUSTSTORE.get())
280                      .valuePlaceholder(INFO_TRUSTSTOREPATH_PLACEHOLDER.get())
281                      .buildAndAddToParser(argParser);
282      sslTrustStorePIN =
283              StringArgument.builder(OPTION_LONG_TRUSTSTORE_PWD)
284                      .description(INFO_LDAPPWMOD_DESCRIPTION_TRUSTSTORE_PIN.get())
285                      .valuePlaceholder(INFO_TRUSTSTORE_PWD_PLACEHOLDER.get())
286                      .buildAndAddToParser(argParser);
287      sslTrustStorePINFile =
288              FileBasedArgument.builder(OPTION_LONG_TRUSTSTORE_PWD_FILE)
289                      .shortIdentifier(OPTION_SHORT_TRUSTSTORE_PWD_FILE)
290                      .description(INFO_LDAPPWMOD_DESCRIPTION_TRUSTSTORE_PINFILE.get())
291                      .valuePlaceholder(INFO_TRUSTSTORE_PWD_FILE_PLACEHOLDER.get())
292                      .buildAndAddToParser(argParser);
293      controlStr =
294              StringArgument.builder("control")
295                      .shortIdentifier('J')
296                      .description(INFO_DESCRIPTION_CONTROLS.get())
297                      .multiValued()
298                      .valuePlaceholder(INFO_LDAP_CONTROL_PLACEHOLDER.get())
299                      .buildAndAddToParser(argParser);
300      connectTimeout =
301              IntegerArgument.builder(OPTION_LONG_CONNECT_TIMEOUT)
302                      .description(INFO_DESCRIPTION_CONNECTION_TIMEOUT.get())
303                      .lowerBound(0)
304                      .defaultValue(CliConstants.DEFAULT_LDAP_CONNECT_TIMEOUT)
305                      .valuePlaceholder(INFO_TIMEOUT_PLACEHOLDER.get())
306                      .buildAndAddToParser(argParser);
307
308      showUsage = showUsageArgument();
309      argParser.addArgument(showUsage);
310      argParser.setUsageArgument(showUsage, out);
311    }
312    catch (ArgumentException ae)
313    {
314      printWrappedText(err, ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage()));
315      return CLIENT_SIDE_PARAM_ERROR;
316    }
317
318    // Parse the command-line arguments provided to this program.
319    try
320    {
321      argParser.parseArguments(args);
322    }
323    catch (ArgumentException ae)
324    {
325      argParser.displayMessageAndUsageReference(err, ERR_ERROR_PARSING_ARGS.get(ae.getMessage()));
326      return CLIENT_SIDE_PARAM_ERROR;
327    }
328
329    // If the usage or version argument was provided,
330    // then we don't need to do anything else.
331    if (argParser.usageOrVersionDisplayed())
332    {
333      return 0;
334    }
335
336    // Make sure that the user didn't specify any conflicting arguments.
337    try
338    {
339      throwIfArgumentsConflict(bindPW, bindPWFile);
340      throwIfArgumentsConflict(newPW, newPWFile);
341      throwIfArgumentsConflict(currentPW, currentPWFile);
342      throwIfArgumentsConflict(useSSL, useStartTLS);
343      throwIfArgumentsConflict(sslKeyStorePIN, sslKeyStorePINFile);
344      throwIfArgumentsConflict(sslTrustStorePIN, sslTrustStorePINFile);
345    }
346    catch(final ArgumentException conflict)
347    {
348      printWrappedText(err, conflict.getMessageObject());
349      return CLIENT_SIDE_PARAM_ERROR;
350    }
351
352    // If a bind DN was provided, make sure that a password was given.  If a
353    // password was given, make sure a bind DN was provided.  If neither were
354    // given, then make sure that an authorization ID and the current password
355    // were provided.
356    if (bindDN.isPresent())
357    {
358      if (!bindPW.isPresent() && !bindPWFile.isPresent())
359      {
360        argParser.displayMessageAndUsageReference(err, ERR_LDAPPWMOD_BIND_DN_AND_PW_MUST_BE_TOGETHER.get());
361        return CLIENT_SIDE_PARAM_ERROR;
362      }
363    }
364    else if (bindPW.isPresent() || bindPWFile.isPresent())
365    {
366      argParser.displayMessageAndUsageReference(err, ERR_LDAPPWMOD_BIND_DN_AND_PW_MUST_BE_TOGETHER.get());
367      return CLIENT_SIDE_PARAM_ERROR;
368    }
369    else
370    {
371      if (provideDNForAuthzID.isPresent())
372      {
373        argParser.displayMessageAndUsageReference(err,
374            ERR_LDAPPWMOD_DEPENDENT_ARGS.get(provideDNForAuthzID.getLongIdentifier(), bindDN.getLongIdentifier()));
375        return CLIENT_SIDE_PARAM_ERROR;
376      }
377
378      if (!authzID.isPresent() || (!currentPW.isPresent() && !currentPWFile.isPresent()))
379      {
380        argParser.displayMessageAndUsageReference(err, ERR_LDAPPWMOD_ANON_REQUIRES_AUTHZID_AND_CURRENTPW.get());
381        return CLIENT_SIDE_PARAM_ERROR;
382      }
383    }
384
385    // Get the host and port.
386    String host = ldapHost.getValue();
387    int    port;
388    try
389    {
390      port = ldapPort.getIntValue();
391    }
392    catch (Exception e)
393    {
394      // This should never happen.
395      printWrappedText(err, e.toString());
396      return CLIENT_SIDE_PARAM_ERROR;
397    }
398
399    // If a control string was provided, then decode the requested controls.
400    ArrayList<Control> controls = new ArrayList<>();
401    if(controlStr.isPresent())
402    {
403      for (String ctrlString : controlStr.getValues())
404      {
405        LDAPControl ctrl = LDAPToolUtils.getControl(ctrlString, err);
406        if(ctrl == null)
407        {
408          printWrappedText(err, ERR_TOOL_INVALID_CONTROL_STRING.get(ctrlString));
409          return CLIENT_SIDE_PARAM_ERROR;
410        }
411        controls.add(ctrl);
412      }
413    }
414
415    // Perform a basic Directory Server bootstrap if appropriate.
416    if (initializeServer)
417    {
418      EmbeddedUtils.initializeForClientUse();
419    }
420
421    // Establish a connection to the Directory Server.
422    AtomicInteger nextMessageID = new AtomicInteger(1);
423    LDAPConnectionOptions connectionOptions = new LDAPConnectionOptions();
424    connectionOptions.setUseSSL(useSSL.isPresent());
425    connectionOptions.setStartTLS(useStartTLS.isPresent());
426    connectionOptions.setVersionNumber(3);
427    if(connectionOptions.useSSL() || connectionOptions.useStartTLS())
428    {
429      String keyPIN = null;
430      if (sslKeyStorePIN.isPresent())
431      {
432        keyPIN = sslKeyStorePIN.getValue();
433      }
434      else if (sslKeyStorePINFile.isPresent())
435      {
436        keyPIN = sslKeyStorePINFile.getValue();
437      }
438
439      String trustPIN = null;
440      if (sslTrustStorePIN.isPresent())
441      {
442        trustPIN = sslTrustStorePIN.getValue();
443      }
444      else if (sslTrustStorePINFile.isPresent())
445      {
446        trustPIN = sslTrustStorePINFile.getValue();
447      }
448
449      try
450      {
451        String clientAlias;
452        if (certNickname.isPresent())
453        {
454          clientAlias = certNickname.getValue();
455        }
456        else
457        {
458          clientAlias = null;
459        }
460        SSLConnectionFactory sslConnectionFactory = new SSLConnectionFactory();
461        sslConnectionFactory.init(trustAll.isPresent(),
462                                  sslKeyStore.getValue(), keyPIN, clientAlias,
463                                  sslTrustStore.getValue(), trustPIN);
464        connectionOptions.setSSLConnectionFactory(sslConnectionFactory);
465      }
466      catch (Exception e)
467      {
468        printWrappedText(err, ERR_LDAPPWMOD_ERROR_INITIALIZING_SSL.get(e));
469        return CLIENT_SIDE_PARAM_ERROR;
470      }
471    }
472
473    LDAPConnection connection = new LDAPConnection(host, port,
474                                                   connectionOptions, out, err);
475    String dn;
476    String pw;
477    if (bindPW.isPresent())
478    {
479      dn = bindDN.getValue();
480      pw = bindPW.getValue();
481      if ("-".equals(pw))
482      {
483        // read the password from the stdin.
484        try
485        {
486          out.print(INFO_LDAPAUTH_PASSWORD_PROMPT.get(dn));
487          char[] pwChars = ConsoleApplication.readPassword();
488          //As per rfc 4513(section-5.1.2) a client should avoid sending
489          //an empty password to the server.
490          while(pwChars.length==0)
491          {
492            printWrappedText(err, INFO_LDAPAUTH_NON_EMPTY_PASSWORD.get());
493            out.print(INFO_LDAPAUTH_PASSWORD_PROMPT.get(dn));
494            pwChars = ConsoleApplication.readPassword();
495          }
496          pw = new String(pwChars);
497        } catch(Exception ex)
498        {
499          printWrappedText(err, ex.getMessage());
500          return CLIENT_SIDE_PARAM_ERROR;
501        }
502      }
503    }
504    else if (bindPWFile.isPresent())
505    {
506      dn = bindDN.getValue();
507      pw = bindPWFile.getValue();
508    }
509    else
510    {
511      dn = null;
512      pw = null;
513    }
514
515    try
516    {
517      int timeout = connectTimeout.getIntValue();
518      connection.connectToHost(dn, pw, nextMessageID, timeout);
519    }
520    catch (LDAPConnectionException lce)
521    {
522      printWrappedText(err, ERR_LDAPPWMOD_CANNOT_CONNECT.get(lce.getMessage()));
523      return lce.getResultCode();
524    }
525    catch (ArgumentException e)
526    {
527      // This should not occur because the arguments are already parsed.
528      // It is a bug
529      e.printStackTrace();
530      throw new IllegalStateException("Unexpected error: "+e, e);
531    }
532
533    LDAPReader reader = connection.getLDAPReader();
534    LDAPWriter writer = connection.getLDAPWriter();
535
536    // Construct the password modify request.
537    ByteStringBuilder builder = new ByteStringBuilder();
538    ASN1Writer asn1Writer = ASN1.getWriter(builder);
539
540    try
541    {
542    asn1Writer.writeStartSequence();
543    if (authzID.isPresent())
544    {
545      asn1Writer.writeOctetString(TYPE_PASSWORD_MODIFY_USER_ID,
546          authzID.getValue());
547    }
548    else if (provideDNForAuthzID.isPresent())
549    {
550      asn1Writer.writeOctetString(TYPE_PASSWORD_MODIFY_USER_ID, "dn:" + dn);
551    }
552
553    if (currentPW.isPresent())
554    {
555      asn1Writer.writeOctetString(TYPE_PASSWORD_MODIFY_OLD_PASSWORD,
556                                              currentPW.getValue());
557    }
558    else if (currentPWFile.isPresent())
559    {
560      asn1Writer.writeOctetString(TYPE_PASSWORD_MODIFY_OLD_PASSWORD,
561                                              currentPWFile.getValue());
562    }
563    else if (provideDNForAuthzID.isPresent())
564    {
565      asn1Writer.writeOctetString(TYPE_PASSWORD_MODIFY_OLD_PASSWORD,
566                                              pw);
567    }
568
569    if (newPW.isPresent())
570    {
571      asn1Writer.writeOctetString(TYPE_PASSWORD_MODIFY_NEW_PASSWORD,
572                                              newPW.getValue());
573    }
574    else if (newPWFile.isPresent())
575    {
576      asn1Writer.writeOctetString(TYPE_PASSWORD_MODIFY_NEW_PASSWORD,
577                                              newPWFile.getValue());
578    }
579    asn1Writer.writeEndSequence();
580    }
581    catch(Exception e)
582    {
583      err.println(e);
584    }
585
586    ExtendedRequestProtocolOp extendedRequest =
587         new ExtendedRequestProtocolOp(OID_PASSWORD_MODIFY_REQUEST,
588                                       builder.toByteString());
589    LDAPMessage requestMessage =
590         new LDAPMessage(nextMessageID.getAndIncrement(), extendedRequest,
591                         controls);
592
593    // Send the request to the server and read the response.
594    try
595    {
596      writer.writeMessage(requestMessage);
597    }
598    catch (Exception e)
599    {
600      printWrappedText(err, ERR_LDAPPWMOD_CANNOT_SEND_PWMOD_REQUEST.get(e));
601      unbind(nextMessageID, writer);
602      close(reader, writer);
603      return 1;
604    }
605
606    // Read the response from the server.
607    LDAPMessage responseMessage = null;
608    try
609    {
610      responseMessage = reader.readMessage();
611    }
612    catch (Exception e)
613    {
614      printWrappedText(err, ERR_LDAPPWMOD_CANNOT_READ_PWMOD_RESPONSE.get(e));
615      unbind(nextMessageID, writer);
616      close(reader, writer);
617      return 1;
618    }
619
620    // Make sure that the response was acceptable.
621    ExtendedResponseProtocolOp extendedResponse =
622         responseMessage.getExtendedResponseProtocolOp();
623    int resultCode = extendedResponse.getResultCode();
624    if (resultCode != LDAPResultCode.SUCCESS)
625    {
626      printWrappedText(err, ERR_LDAPPWMOD_FAILED.get(resultCode));
627
628      LocalizableMessage errorMessage = extendedResponse.getErrorMessage();
629      if (errorMessage != null && errorMessage.length() > 0)
630      {
631        printWrappedText(err, ERR_LDAPPWMOD_FAILURE_ERROR_MESSAGE.get(errorMessage));
632      }
633
634      DN matchedDN = extendedResponse.getMatchedDN();
635      if (matchedDN != null)
636      {
637        printWrappedText(err, ERR_LDAPPWMOD_FAILURE_MATCHED_DN.get(matchedDN));
638      }
639
640      unbind(nextMessageID, writer);
641      close(reader, writer);
642      return resultCode;
643    }
644    else
645    {
646      printWrappedText(out, INFO_LDAPPWMOD_SUCCESSFUL.get());
647      LocalizableMessage additionalInfo = extendedResponse.getErrorMessage();
648      if (additionalInfo != null && additionalInfo.length() > 0)
649      {
650        printWrappedText(out, INFO_LDAPPWMOD_ADDITIONAL_INFO.get(additionalInfo));
651      }
652    }
653
654    // See if the response included any controls that we recognize, and if so
655    // then handle them.
656    for (Control c : responseMessage.getControls())
657    {
658      if (c.getOID().equals(OID_PASSWORD_POLICY_CONTROL))
659      {
660        try
661        {
662          PasswordPolicyResponseControl pwPolicyControl =
663            PasswordPolicyResponseControl.DECODER
664              .decode(c.isCritical(), ((LDAPControl) c).getValue());
665
666          PasswordPolicyWarningType pwPolicyWarningType = pwPolicyControl.getWarningType();
667          if (pwPolicyWarningType != null)
668          {
669            printWrappedText(
670                    out, INFO_LDAPPWMOD_PWPOLICY_WARNING.get(pwPolicyWarningType, pwPolicyControl.getWarningValue()));
671          }
672
673          PasswordPolicyErrorType pwPolicyErrorType = pwPolicyControl.getErrorType();
674          if (pwPolicyErrorType != null)
675          {
676            printWrappedText(out, INFO_LDAPPWMOD_PWPOLICY_ERROR.get(pwPolicyErrorType));
677          }
678        }
679        catch (Exception e)
680        {
681          printWrappedText(err, ERR_LDAPPWMOD_CANNOT_DECODE_PWPOLICY_CONTROL.get(e));
682        }
683      }
684    }
685
686    // See if the response included a generated password.
687    ByteString responseValue = extendedResponse.getValue();
688    if (responseValue != null)
689    {
690      try
691      {
692        ASN1Reader asn1Reader = ASN1.getReader(responseValue);
693        asn1Reader.readStartSequence();
694        while(asn1Reader.hasNextElement())
695        {
696          if (asn1Reader.peekType() == TYPE_PASSWORD_MODIFY_GENERATED_PASSWORD)
697          {
698            printWrappedText(out, INFO_LDAPPWMOD_GENERATED_PASSWORD.get(asn1Reader.readOctetStringAsString()));
699          }
700          else
701          {
702            printWrappedText(err, ERR_LDAPPWMOD_UNRECOGNIZED_VALUE_TYPE.get(asn1Reader.readOctetStringAsString()));
703          }
704        }
705        asn1Reader.readEndSequence();
706      }
707      catch (Exception e)
708      {
709        printWrappedText(err, ERR_LDAPPWMOD_COULD_NOT_DECODE_RESPONSE_VALUE.get(e));
710        unbind(nextMessageID, writer);
711        close(reader, writer);
712        return 1;
713      }
714    }
715
716    // Unbind from the server and close the connection.
717    unbind(nextMessageID, writer);
718    close(reader, writer);
719    return 0;
720  }
721
722  private static void unbind(AtomicInteger nextMessageID, LDAPWriter writer)
723  {
724    try
725    {
726      LDAPMessage requestMessage = new LDAPMessage(
727          nextMessageID.getAndIncrement(), new UnbindRequestProtocolOp());
728      writer.writeMessage(requestMessage);
729    }
730    catch (Exception e) {}
731  }
732}