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