001/*
002 * Copyright 2020-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2020-2024 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2020-2024 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.ldap.sdk.unboundidds.tools;
037
038
039
040import java.io.File;
041import java.io.OutputStream;
042import java.nio.charset.StandardCharsets;
043import java.security.SecureRandom;
044import java.util.ArrayList;
045import java.util.Arrays;
046import java.util.Collections;
047import java.util.LinkedHashMap;
048import java.util.List;
049import java.util.Set;
050import java.util.concurrent.TimeUnit;
051import java.util.concurrent.atomic.AtomicReference;
052
053import com.unboundid.ldap.sdk.Control;
054import com.unboundid.ldap.sdk.DN;
055import com.unboundid.ldap.sdk.ExtendedResult;
056import com.unboundid.ldap.sdk.Filter;
057import com.unboundid.ldap.sdk.LDAPConnection;
058import com.unboundid.ldap.sdk.LDAPConnectionOptions;
059import com.unboundid.ldap.sdk.LDAPConnectionPool;
060import com.unboundid.ldap.sdk.LDAPException;
061import com.unboundid.ldap.sdk.LDAPResult;
062import com.unboundid.ldap.sdk.Modification;
063import com.unboundid.ldap.sdk.ModificationType;
064import com.unboundid.ldap.sdk.ModifyRequest;
065import com.unboundid.ldap.sdk.ResultCode;
066import com.unboundid.ldap.sdk.RootDSE;
067import com.unboundid.ldap.sdk.SearchRequest;
068import com.unboundid.ldap.sdk.SearchResult;
069import com.unboundid.ldap.sdk.SearchScope;
070import com.unboundid.ldap.sdk.UnsolicitedNotificationHandler;
071import com.unboundid.ldap.sdk.Version;
072import com.unboundid.ldap.sdk.controls.AuthorizationIdentityRequestControl;
073import com.unboundid.ldap.sdk.extensions.PasswordModifyExtendedRequest;
074import com.unboundid.ldap.sdk.extensions.PasswordModifyExtendedResult;
075import com.unboundid.ldap.sdk.extensions.WhoAmIExtendedRequest;
076import com.unboundid.ldap.sdk.extensions.WhoAmIExtendedResult;
077import com.unboundid.ldap.sdk.unboundidds.controls.AssuredReplicationLocalLevel;
078import com.unboundid.ldap.sdk.unboundidds.controls.
079            AssuredReplicationRemoteLevel;
080import com.unboundid.ldap.sdk.unboundidds.controls.
081            AssuredReplicationRequestControl;
082import com.unboundid.ldap.sdk.unboundidds.controls.
083            GetAuthorizationEntryRequestControl;
084import com.unboundid.ldap.sdk.unboundidds.controls.
085            GetUserResourceLimitsRequestControl;
086import com.unboundid.ldap.sdk.unboundidds.controls.NoOpRequestControl;
087import com.unboundid.ldap.sdk.unboundidds.controls.
088            OperationPurposeRequestControl;
089import com.unboundid.ldap.sdk.unboundidds.controls.
090            PasswordPolicyRequestControl;
091import com.unboundid.ldap.sdk.unboundidds.controls.
092            PasswordValidationDetailsRequestControl;
093import com.unboundid.ldap.sdk.unboundidds.controls.PurgePasswordRequestControl;
094import com.unboundid.ldap.sdk.unboundidds.controls.RetirePasswordRequestControl;
095import com.unboundid.ldap.sdk.unboundidds.extensions.
096            StartAdministrativeSessionExtendedRequest;
097import com.unboundid.ldap.sdk.unboundidds.extensions.
098            StartAdministrativeSessionPostConnectProcessor;
099import com.unboundid.util.Debug;
100import com.unboundid.util.LDAPCommandLineTool;
101import com.unboundid.util.NotNull;
102import com.unboundid.util.Nullable;
103import com.unboundid.util.PasswordReader;
104import com.unboundid.util.StaticUtils;
105import com.unboundid.util.ThreadLocalSecureRandom;
106import com.unboundid.util.ThreadSafety;
107import com.unboundid.util.ThreadSafetyLevel;
108import com.unboundid.util.args.ArgumentException;
109import com.unboundid.util.args.ArgumentParser;
110import com.unboundid.util.args.BooleanArgument;
111import com.unboundid.util.args.ControlArgument;
112import com.unboundid.util.args.DNArgument;
113import com.unboundid.util.args.DurationArgument;
114import com.unboundid.util.args.FileArgument;
115import com.unboundid.util.args.IntegerArgument;
116import com.unboundid.util.args.StringArgument;
117
118import static com.unboundid.ldap.sdk.unboundidds.tools.ToolMessages.*;
119
120
121
122/**
123 * This class provides an implementation of an LDAP command-line tool that may
124 * be used to change passwords in a directory server.  Three types of password
125 * changes are supported:  the password modify extended operation (as described
126 * in <A HREF="http://www.ietf.org/rfc/rfc3062.txt">RFC 3062</A>), a standard
127 * LDAP modify operation that targets an attribute like userPassword, or an
128 * Active Directory-specific password change that uses an LDAP modify operation
129 * to replace the value of the unicodePwd attribute with a value that is the
130 * password surrounded by quotation marks and encoded with UTF-16-LE.
131 * <BR>
132 * <BLOCKQUOTE>
133 *   <B>NOTE:</B>  This class, and other classes within the
134 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
135 *   supported for use against Ping Identity, UnboundID, and
136 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
137 *   for proprietary functionality or for external specifications that are not
138 *   considered stable or mature enough to be guaranteed to work in an
139 *   interoperable way with other types of LDAP servers.
140 * </BLOCKQUOTE>
141 */
142@ThreadSafety(level=ThreadSafetyLevel.NOT_THREADSAFE)
143public final class LDAPPasswordModify
144       extends LDAPCommandLineTool
145       implements UnsolicitedNotificationHandler
146{
147  /**
148   * The column at which output should be wrapped.
149   */
150  private static final int WRAP_COLUMN = StaticUtils.TERMINAL_WIDTH_COLUMNS - 1;
151
152
153
154  /**
155   * The assured replication local level value that indicates no assurance is
156   * needed.
157   */
158  @NotNull private static final String ASSURED_REPLICATION_LOCAL_LEVEL_NONE =
159       "none";
160
161
162
163  /**
164   * The assured replication local level value that indicates the change should
165   * be received by at least one other local server.
166   */
167  @NotNull private static final String
168       ASSURED_REPLICATION_LOCAL_LEVEL_RECEIVED_ANY_SERVER =
169            "received-any-server";
170
171
172
173  /**
174   * The assured replication local level value that indicates the change should
175   * be processed by all available local servers.
176   */
177  @NotNull private static final String
178       ASSURED_REPLICATION_LOCAL_LEVEL_PROCESSED_ALL_SERVERS =
179            "processed-all-servers";
180
181
182
183  /**
184   * The assured replication remote level value that indicates no assurance is
185   * needed.
186   */
187  @NotNull private static final String ASSURED_REPLICATION_REMOTE_LEVEL_NONE =
188       "none";
189
190
191
192  /**
193   * The assured replication remote level value that indicates the change should
194   * be received by at least one other remote server in at least one remote
195   * location.
196   */
197  @NotNull private static final String
198       ASSURED_REPLICATION_REMOTE_LEVEL_RECEIVED_ANY_REMOTE_LOCATION =
199            "received-any-remote-location";
200
201
202
203  /**
204   * The assured replication remote level value that indicates the change should
205   * be received by at least one other remote server in every remote
206   * location.
207   */
208  @NotNull private static final String
209       ASSURED_REPLICATION_REMOTE_LEVEL_RECEIVED_ALL_REMOTE_LOCATIONS =
210            "received-all-remote-locations";
211
212
213
214  /**
215   * The assured replication remote level value that indicates the change should
216   * be processed by all available remote servers in all locations.
217   */
218  @NotNull private static final String
219       ASSURED_REPLICATION_REMOTE_LEVEL_PROCESSED_ALL_REMOTE_SERVERS =
220            "processed-all-remote-servers";
221
222
223
224  /**
225   * The password change method that will be used to indicate that the password
226   * modify extended operation should be used.
227   */
228  @NotNull private static final String PASSWORD_CHANGE_METHOD_PW_MOD_EXTOP =
229       "password-modify-extended-operation";
230
231
232
233  /**
234   * The password change method that will be used to indicate that a regular
235   * LDAP modify operation should be used.
236   */
237  @NotNull private static final String PASSWORD_CHANGE_METHOD_LDAP_MOD =
238       "ldap-modify";
239
240
241
242  /**
243   * The password change method that will be used to indicate that an
244   * Active Directory-specific operation should be used.
245   */
246  @NotNull private static final String PASSWORD_CHANGE_METHOD_AD =
247       "active-directory";
248
249
250
251  /**
252   * The long identifier for the {@link LDAPCommandLineTool} argument used to
253   * specify the bind DN to use when authenticating to the directory server.
254   */
255  @NotNull private static final String BIND_DN_ARGUMENT_LONG_IDENTIFIER =
256       "bindDN";
257
258
259
260  /**
261   * The name of the default attribute that will be assumed to hold the password
262   * in most directory servers.
263   */
264  @NotNull private static final String DEFAULT_PASSWORD_ATTRIBUTE =
265       "userPassword";
266
267
268
269  /**
270   * The name of the attribute that Active Directory uses to hold the password.
271   */
272  @NotNull private static final String AD_PASSWORD_ATTRIBUTE = "unicodePwd";
273
274
275
276  /**
277   * The names of the attributes that will be used when searching for an entry
278   * from its username in most directory servers.
279   */
280  @NotNull private static final List<String> DEFAULT_USERNAME_ATTRIBUTES =
281       Collections.singletonList("uid");
282
283
284
285  /**
286   * The names of the attributes that will be used when searching for an entry
287   * from its username in an Active Directory server.
288   */
289  @NotNull private static final List<String> AD_USERNAME_ATTRIBUTES =
290       Collections.unmodifiableList(Arrays.asList("samAccountName",
291            "userPrincipalName"));
292
293
294
295  /**
296   * The OID base that has been assigned to Microsoft.
297   */
298  @NotNull private static final String MICROSOFT_BASE_OBJECT_IDENTIFIER =
299       "1.2.840.113556";
300
301
302
303  // A reference to the completion message to return for this tool.
304  @NotNull private final AtomicReference<String> completionMessage;
305
306  // A reference to the argument parser for this tool.
307  @Nullable private ArgumentParser argumentParser;
308
309  // The supported command-line arguments.
310  @Nullable private BooleanArgument followReferrals;
311  @Nullable private BooleanArgument generateClientSideNewPassword;
312  @Nullable private BooleanArgument getPasswordValidationDetails;
313  @Nullable private BooleanArgument getUserResourceLimits;
314  @Nullable private BooleanArgument noOperation;
315  @Nullable private BooleanArgument promptForCurrentPassword;
316  @Nullable private BooleanArgument promptForNewPassword;
317  @Nullable private BooleanArgument provideBindDNAsUserIdentity;
318  @Nullable private BooleanArgument purgeCurrentPassword;
319  @Nullable private BooleanArgument retireCurrentPassword;
320  @Nullable private BooleanArgument scriptFriendly;
321  @Nullable private BooleanArgument useAdministrativeSession;
322  @Nullable private BooleanArgument useAssuredReplication;
323  @Nullable private BooleanArgument useAuthorizationIdentityControl;
324  @Nullable private BooleanArgument usePasswordPolicyControlOnBind;
325  @Nullable private BooleanArgument usePasswordPolicyControlOnUpdate;
326  @Nullable private BooleanArgument verbose;
327  @Nullable private ControlArgument bindControl;
328  @Nullable private ControlArgument updateControl;
329  @Nullable private DNArgument searchBaseDN;
330  @Nullable private DurationArgument assuredReplicationTimeout;
331  @Nullable private FileArgument currentPasswordFile;
332  @Nullable private FileArgument newPasswordFile;
333  @Nullable private IntegerArgument generatedPasswordLength;
334  @Nullable private StringArgument assuredReplicationLocalLevel;
335  @Nullable private StringArgument assuredReplicationRemoteLevel;
336  @Nullable private StringArgument currentPassword;
337  @Nullable private StringArgument generatedPasswordCharacterSet;
338  @Nullable private StringArgument getAuthorizationEntryAttribute;
339  @Nullable private StringArgument newPassword;
340  @Nullable private StringArgument operationPurpose;
341  @Nullable private StringArgument passwordAttribute;
342  @Nullable private StringArgument passwordChangeMethod;
343  @Nullable private StringArgument passwordUpdateBehavior;
344  @Nullable private StringArgument userIdentity;
345  @Nullable private StringArgument usernameAttribute;
346
347
348
349
350  /**
351   * Invokes this tool with the provided set of arguments.  The default standard
352   * output and error streams will be used.
353   *
354   * @param  args  The command-line arguments provided to this program.
355   */
356  public static void main(@NotNull final String... args)
357  {
358    final ResultCode resultCode = main(System.out, System.err, args);
359    if (resultCode != ResultCode.SUCCESS)
360    {
361      System.exit(resultCode.intValue());
362    }
363  }
364
365
366
367  /**
368   * Invokes this tool with the provided set of arguments, and using the
369   * provided streams for standard output and error.
370   *
371   * @param  out   The output stream to use for standard output.  It may be
372   *               {@code null} if standard output should be suppressed.
373   * @param  err   The output stream to use for standard error.  It may be
374   *               {@code null} if standard error should be suppressed.
375   * @param  args  The command-line arguments provided to this program.
376   *
377   * @return  The result code obtained when running the tool.  Any result code
378   *          other than {@link ResultCode#SUCCESS} indicates an error.
379   */
380  @NotNull()
381  public static ResultCode main(@Nullable final OutputStream out,
382                                @Nullable final OutputStream err,
383                                @NotNull final String... args)
384  {
385    final LDAPPasswordModify tool = new LDAPPasswordModify(out, err);
386    return tool.runTool(args);
387  }
388
389
390
391  /**
392   * Creates a new instance of this tool with the provided output and error
393   * streams.
394   *
395   * @param  out  The output stream to use for standard output.  It may be
396   *              {@code null} if standard output should be suppressed.
397   * @param  err  The output stream to use for standard error.  It may be
398   *              {@code null} if standard error should be suppressed.
399   */
400  public LDAPPasswordModify(@Nullable final OutputStream out,
401                            @Nullable final OutputStream err)
402  {
403    super(out, err);
404
405    completionMessage = new AtomicReference<>();
406
407    argumentParser = null;
408
409    followReferrals = null;
410    generateClientSideNewPassword = null;
411    getPasswordValidationDetails = null;
412    getUserResourceLimits = null;
413    noOperation = null;
414    promptForCurrentPassword = null;
415    promptForNewPassword = null;
416    provideBindDNAsUserIdentity = null;
417    purgeCurrentPassword = null;
418    retireCurrentPassword = null;
419    scriptFriendly = null;
420    useAdministrativeSession = null;
421    useAssuredReplication = null;
422    useAuthorizationIdentityControl = null;
423    usePasswordPolicyControlOnBind = null;
424    usePasswordPolicyControlOnUpdate = null;
425    verbose = null;
426    bindControl = null;
427    updateControl = null;
428    searchBaseDN = null;
429    assuredReplicationTimeout = null;
430    currentPasswordFile = null;
431    newPasswordFile = null;
432    generatedPasswordLength = null;
433    assuredReplicationLocalLevel = null;
434    assuredReplicationRemoteLevel = null;
435    currentPassword = null;
436    generatedPasswordCharacterSet = null;
437    getAuthorizationEntryAttribute = null;
438    newPassword = null;
439    operationPurpose = null;
440    passwordAttribute = null;
441    passwordChangeMethod = null;
442    passwordUpdateBehavior = null;
443    userIdentity = null;
444    usernameAttribute = null;
445  }
446
447
448
449  /**
450   * {@inheritDoc}
451   */
452  @Override()
453  @NotNull()
454  public String getToolName()
455  {
456    return "ldappasswordmodify";
457  }
458
459
460
461  /**
462   * {@inheritDoc}
463   */
464  @Override()
465  @NotNull()
466  public String getToolDescription()
467  {
468    return INFO_PWMOD_TOOL_DESCRIPTION_1.get();
469  }
470
471
472
473  /**
474   * {@inheritDoc}
475   */
476  @Override()
477  @NotNull()
478  public List<String> getAdditionalDescriptionParagraphs()
479  {
480    return Collections.unmodifiableList(Arrays.asList(
481         INFO_PWMOD_TOOL_DESCRIPTION_2.get(),
482         INFO_PWMOD_TOOL_DESCRIPTION_3.get(),
483         INFO_PWMOD_TOOL_DESCRIPTION_4.get()));
484  }
485
486
487
488  /**
489   * {@inheritDoc}
490   */
491  @Override()
492  @NotNull()
493  public String getToolVersion()
494  {
495    return Version.NUMERIC_VERSION_STRING;
496  }
497
498
499
500  /**
501   * {@inheritDoc}
502   */
503  @Override()
504  public boolean supportsInteractiveMode()
505  {
506    return true;
507  }
508
509
510
511  /**
512   * {@inheritDoc}
513   */
514  @Override()
515  public boolean defaultsToInteractiveMode()
516  {
517    return true;
518  }
519
520
521
522  /**
523   * {@inheritDoc}
524   */
525  @Override()
526  public boolean supportsPropertiesFile()
527  {
528    return true;
529  }
530
531
532
533  /**
534   * {@inheritDoc}
535   */
536  @Override()
537  protected boolean supportsOutputFile()
538  {
539    return true;
540  }
541
542
543
544  /**
545   * {@inheritDoc}
546   */
547  @Override()
548  protected boolean supportsAuthentication()
549  {
550    return true;
551  }
552
553
554
555  /**
556   * {@inheritDoc}
557   */
558  @Override()
559  protected boolean defaultToPromptForBindPassword()
560  {
561    return true;
562  }
563
564
565
566  /**
567   * {@inheritDoc}
568   */
569  @Override()
570  protected boolean supportsSASLHelp()
571  {
572    return true;
573  }
574
575
576
577  /**
578   * {@inheritDoc}
579   */
580  @Override()
581  protected boolean includeAlternateLongIdentifiers()
582  {
583    return true;
584  }
585
586
587
588  /**
589   * {@inheritDoc}
590   */
591  @Override()
592  @NotNull()
593  protected List<Control> getBindControls()
594  {
595    final List<Control> bindControls = new ArrayList<>(10);
596
597    if (bindControl.isPresent())
598    {
599      bindControls.addAll(bindControl.getValues());
600    }
601
602    if (useAuthorizationIdentityControl.isPresent())
603    {
604      bindControls.add(new AuthorizationIdentityRequestControl(false));
605    }
606
607    if (getAuthorizationEntryAttribute.isPresent())
608    {
609      bindControls.add(new GetAuthorizationEntryRequestControl(true, true,
610           getAuthorizationEntryAttribute.getValues()));
611    }
612
613    if (getUserResourceLimits.isPresent())
614    {
615      bindControls.add(new GetUserResourceLimitsRequestControl());
616    }
617
618    if (usePasswordPolicyControlOnBind.isPresent())
619    {
620      bindControls.add(new PasswordPolicyRequestControl());
621    }
622
623    return bindControls;
624  }
625
626
627
628  /**
629   * {@inheritDoc}
630   */
631  @Override()
632  protected boolean supportsMultipleServers()
633  {
634    return true;
635  }
636
637
638
639  /**
640   * {@inheritDoc}
641   */
642  @Override()
643  protected boolean supportsSSLDebugging()
644  {
645    return true;
646  }
647
648
649
650  /**
651   * {@inheritDoc}
652   */
653  @Override()
654  @NotNull()
655  public LDAPConnectionOptions getConnectionOptions()
656  {
657    final LDAPConnectionOptions options = new LDAPConnectionOptions();
658
659    options.setUseSynchronousMode(true);
660    options.setFollowReferrals(followReferrals.isPresent());
661    options.setUnsolicitedNotificationHandler(this);
662    options.setResponseTimeoutMillis(0L);
663
664    return options;
665  }
666
667
668
669  /**
670   * {@inheritDoc}
671   */
672  @Override()
673  protected boolean logToolInvocationByDefault()
674  {
675    return true;
676  }
677
678
679
680  /**
681   * {@inheritDoc}
682   */
683  @Override()
684  @Nullable()
685  protected String getToolCompletionMessage()
686  {
687    return completionMessage.get();
688  }
689
690
691
692  /**
693   * {@inheritDoc}
694   */
695  @Override()
696  public void addNonLDAPArguments(@NotNull final ArgumentParser parser)
697         throws ArgumentException
698  {
699    argumentParser = parser;
700
701    // Authorization identity arguments.
702    userIdentity = new StringArgument('a', "userIdentity", false, 1,
703         INFO_PWMOD_ARG_PLACEHOLDER_DN_OR_AUTHZID.get(),
704         INFO_PWMOD_ARG_DESC_USER_IDENTITY.get());
705    userIdentity.addLongIdentifier("user-identity", true);
706    userIdentity.addLongIdentifier("userDN", true);
707    userIdentity.addLongIdentifier("user-dn", true);
708    userIdentity.addLongIdentifier("authzID", true);
709    userIdentity.addLongIdentifier("authz-id", true);
710    userIdentity.addLongIdentifier("authorizationID", true);
711    userIdentity.addLongIdentifier("authorization-id", true);
712    userIdentity.setArgumentGroupName(INFO_PWMOD_ARG_GROUP_USER_IDENTITY.get());
713    parser.addArgument(userIdentity);
714
715    provideBindDNAsUserIdentity = new BooleanArgument('A',
716         "provideBindDNAsUserIdentity", 1,
717         INFO_PWMOD_ARG_DESC_PROVIDE_BIND_DN_AS_USER_IDENTITY.get());
718    provideBindDNAsUserIdentity.addLongIdentifier(
719         "provide-bind-dn-as-user-identity", true);
720    provideBindDNAsUserIdentity.addLongIdentifier(
721         "provideBindDNForUserIdentity", true);
722    provideBindDNAsUserIdentity.addLongIdentifier(
723         "provide-bind-dn-for-user-identity", true);
724    provideBindDNAsUserIdentity.addLongIdentifier("provideDNAsUserIdentity",
725         true);
726    provideBindDNAsUserIdentity.addLongIdentifier("provide-dn-as-user-identity",
727         true);
728    provideBindDNAsUserIdentity.addLongIdentifier("provideDNForUserIdentity",
729         true);
730    provideBindDNAsUserIdentity.addLongIdentifier(
731         "provide-dn-for-user-identity", true);
732    provideBindDNAsUserIdentity.addLongIdentifier("useBindDNAsUserIdentity",
733         true);
734    provideBindDNAsUserIdentity.addLongIdentifier(
735         "use-bind-dn-as-user-identity", true);
736    provideBindDNAsUserIdentity.addLongIdentifier("useBindDNForUserIdentity",
737         true);
738    provideBindDNAsUserIdentity.addLongIdentifier(
739         "use-bind-dn-for-user-identity", true);
740    provideBindDNAsUserIdentity.addLongIdentifier("useDNAsUserIdentity", true);
741    provideBindDNAsUserIdentity.addLongIdentifier("use-dn-as-user-identity",
742         true);
743    provideBindDNAsUserIdentity.addLongIdentifier("useDNForUserIdentity", true);
744    provideBindDNAsUserIdentity.addLongIdentifier("use-dn-for-user-identity",
745         true);
746    provideBindDNAsUserIdentity.addLongIdentifier("useBindDNForAuthzID", true);
747    provideBindDNAsUserIdentity.addLongIdentifier("use-bind-dn-for-authz-id",
748         true);
749    provideBindDNAsUserIdentity.addLongIdentifier("provideDNForAuthzID", true);
750    provideBindDNAsUserIdentity.addLongIdentifier("provide-dn-for-authz-id",
751         true);
752    provideBindDNAsUserIdentity.setArgumentGroupName(
753         INFO_PWMOD_ARG_GROUP_USER_IDENTITY.get());
754    parser.addArgument(provideBindDNAsUserIdentity);
755
756    usernameAttribute = new StringArgument(null, "usernameAttribute", false, 0,
757         INFO_PWMOD_ARG_PLACEHOLDER_ATTRIBUTE_NAME.get(),
758         INFO_PWMOD_ARG_DESC_USERNAME_ATTRIBUTE.get());
759    usernameAttribute.addLongIdentifier("username-attribute", true);
760    usernameAttribute.addLongIdentifier("usernameAttr", true);
761    usernameAttribute.addLongIdentifier("username-attr", true);
762    usernameAttribute.addLongIdentifier("userIDAttribute", true);
763    usernameAttribute.addLongIdentifier("user-id-attribute", true);
764    usernameAttribute.addLongIdentifier("userIDAttr", true);
765    usernameAttribute.addLongIdentifier("user-id-attr", true);
766    usernameAttribute.setArgumentGroupName(
767         INFO_PWMOD_ARG_GROUP_USER_IDENTITY.get());
768    parser.addArgument(usernameAttribute);
769
770    searchBaseDN = new DNArgument('b', "searchBaseDN", false, 0, null,
771         INFO_PWMOD_ARG_DESC_SEARCH_BASE_DN.get(), DN.NULL_DN);
772    searchBaseDN.addLongIdentifier("search-base-dn", true);
773    searchBaseDN.addLongIdentifier("baseDN", true);
774    searchBaseDN.addLongIdentifier("base-dn", true);
775    searchBaseDN.setArgumentGroupName(INFO_PWMOD_ARG_GROUP_USER_IDENTITY.get());
776    parser.addArgument(searchBaseDN);
777
778
779    // New password arguments.
780    newPassword = new StringArgument('n', "newPassword", false, 1,
781         INFO_PWMOD_ARG_PLACEHOLDER_PASSWORD.get(),
782         INFO_PWMOD_ARG_DESC_NEW_PASSWORD.get());
783    newPassword.addLongIdentifier("new-password", true);
784    newPassword.addLongIdentifier("newPW", true);
785    newPassword.addLongIdentifier("new-pw", true);
786    newPassword.addLongIdentifier("new", true);
787    newPassword.setArgumentGroupName(INFO_PWMOD_ARG_GROUP_NEW_PASSWORD.get());
788    parser.addArgument(newPassword);
789
790    newPasswordFile = new FileArgument('N', "newPasswordFile", false, 1, null,
791         INFO_PWMOD_ARG_DESC_NEW_PASSWORD_FILE.get(), true, true, true, false);
792    newPasswordFile.addLongIdentifier("new-password-file", true);
793    newPasswordFile.addLongIdentifier("newPWFile", true);
794    newPasswordFile.addLongIdentifier("new-pw-file", true);
795    newPasswordFile.addLongIdentifier("newFile", true);
796    newPasswordFile.addLongIdentifier("new-file", true);
797    newPasswordFile.addLongIdentifier("newPasswordPath", true);
798    newPasswordFile.addLongIdentifier("new-password-path", true);
799    newPasswordFile.addLongIdentifier("newPWPath", true);
800    newPasswordFile.addLongIdentifier("new-pw-path", true);
801    newPasswordFile.addLongIdentifier("newPath", true);
802    newPasswordFile.addLongIdentifier("new-path", true);
803    newPasswordFile.setArgumentGroupName(
804         INFO_PWMOD_ARG_GROUP_NEW_PASSWORD.get());
805    parser.addArgument(newPasswordFile);
806
807    promptForNewPassword = new BooleanArgument(null, "promptForNewPassword", 1,
808         INFO_PWMOD_ARG_DESC_PROMPT_FOR_NEW_PASSWORD.get());
809    promptForNewPassword.addLongIdentifier("prompt-for-new-password", true);
810    promptForNewPassword.addLongIdentifier("promptForNewPW", true);
811    promptForNewPassword.addLongIdentifier("prompt-for-new-pw", true);
812    promptForNewPassword.addLongIdentifier("promptForNew", true);
813    promptForNewPassword.addLongIdentifier("prompt-for-new", true);
814    promptForNewPassword.addLongIdentifier("promptNew", true);
815    promptForNewPassword.addLongIdentifier("prompt-new", true);
816    promptForNewPassword.setArgumentGroupName(
817         INFO_PWMOD_ARG_GROUP_NEW_PASSWORD.get());
818    parser.addArgument(promptForNewPassword);
819
820    generateClientSideNewPassword = new BooleanArgument(null,
821         "generateClientSideNewPassword", 1,
822         INFO_PWMOD_ARG_DESC_GENERATE_CLIENT_SIDE_NEW_PASSWORD.get());
823    generateClientSideNewPassword.addLongIdentifier(
824         "generate-client-side-new-password", true);
825    generateClientSideNewPassword.addLongIdentifier("generateClientSideNewPW",
826         true);
827    generateClientSideNewPassword.addLongIdentifier(
828         "generate-client-side-new-pw", true);
829    generateClientSideNewPassword.addLongIdentifier("generateNewPassword",
830         true);
831    generateClientSideNewPassword.addLongIdentifier("generate-new-password",
832         true);
833    generateClientSideNewPassword.addLongIdentifier("generateNewPW", true);
834    generateClientSideNewPassword.addLongIdentifier("generate-new-pw", true);
835    generateClientSideNewPassword.addLongIdentifier("generatePassword", true);
836    generateClientSideNewPassword.addLongIdentifier("generate-password", true);
837    generateClientSideNewPassword.addLongIdentifier("generatePW", true);
838    generateClientSideNewPassword.addLongIdentifier("generate-pw", true);
839    generateClientSideNewPassword.setArgumentGroupName(
840         INFO_PWMOD_ARG_GROUP_NEW_PASSWORD.get());
841    parser.addArgument(generateClientSideNewPassword);
842
843    generatedPasswordLength = new IntegerArgument(null,
844         "generatedPasswordLength", false, 1,
845         INFO_PWMOD_ARG_PLACEHOLDER_LENGTH.get(),
846         INFO_PWMOD_ARG_DESC_GENERATED_PASSWORD_LENGTH.get(), 1,
847         Integer.MAX_VALUE, 12);
848    generatedPasswordLength.addLongIdentifier("generated-password-length",
849         true);
850    generatedPasswordLength.addLongIdentifier("generatedPWLength", true);
851    generatedPasswordLength.addLongIdentifier("generated-pw-length", true);
852    generatedPasswordLength.addLongIdentifier("passwordLength", true);
853    generatedPasswordLength.addLongIdentifier("password-length", true);
854    generatedPasswordLength.addLongIdentifier("pwLength", true);
855    generatedPasswordLength.addLongIdentifier("pw-length", true);
856    generatedPasswordLength.setArgumentGroupName(
857         INFO_PWMOD_ARG_GROUP_NEW_PASSWORD.get());
858    parser.addArgument(generatedPasswordLength);
859
860    generatedPasswordCharacterSet = new StringArgument(null,
861         "generatedPasswordCharacterSet", false, 0,
862         INFO_PWMOD_ARG_PLACEHOLDER_CHARS.get(),
863         INFO_PWMOD_ARG_DESC_GENERATED_PASSWORD_CHARACTER_SET.get(), null,
864         Collections.unmodifiableList(Arrays.asList(
865              "abcdefghijmnopqrstuvwxyz", // Note that some letters and
866              "ABCDEFGHJLMNPQRSTUVWXYZ",  // digits are missing in an attempt
867              "23456789",                 // to avoid ambiguous characters.
868              "@#-_=+.")));
869    generatedPasswordCharacterSet.addLongIdentifier(
870         "generated-password-character-set", true);
871    generatedPasswordCharacterSet.addLongIdentifier("generatedPWCharacterSet",
872         true);
873    generatedPasswordCharacterSet.addLongIdentifier(
874         "generated-pw-character-set", true);
875    generatedPasswordCharacterSet.addLongIdentifier("generatedPasswordCharSet",
876         true);
877    generatedPasswordCharacterSet.addLongIdentifier(
878         "generated-password-char-set", true);
879    generatedPasswordCharacterSet.addLongIdentifier(
880         "generated-password-charset", true);
881    generatedPasswordCharacterSet.addLongIdentifier("generatedPWCharSet", true);
882    generatedPasswordCharacterSet.addLongIdentifier("generated-pw-char-set",
883         true);
884    generatedPasswordCharacterSet.addLongIdentifier("generated-pw-charset",
885         true);
886    generatedPasswordCharacterSet.addLongIdentifier(
887         "generatedPasswordCharacters", true);
888    generatedPasswordCharacterSet.addLongIdentifier(
889         "generated-password-characters", true);
890    generatedPasswordCharacterSet.addLongIdentifier("generatedPWCharacters",
891         true);
892    generatedPasswordCharacterSet.addLongIdentifier("generated-pw-characters",
893         true);
894    generatedPasswordCharacterSet.addLongIdentifier("generatedPasswordChars",
895         true);
896    generatedPasswordCharacterSet.addLongIdentifier("generated-password-chars",
897         true);
898    generatedPasswordCharacterSet.addLongIdentifier("generatedPWChars", true);
899    generatedPasswordCharacterSet.addLongIdentifier("generated-pw-chars", true);
900    generatedPasswordCharacterSet.addLongIdentifier("passwordCharacters", true);
901    generatedPasswordCharacterSet.addLongIdentifier("password-characters",
902         true);
903    generatedPasswordCharacterSet.addLongIdentifier("pwCharacters", true);
904    generatedPasswordCharacterSet.addLongIdentifier("pw-characters", true);
905    generatedPasswordCharacterSet.addLongIdentifier("passwordCharacterSet",
906         true);
907    generatedPasswordCharacterSet.addLongIdentifier("password-character-set",
908         true);
909    generatedPasswordCharacterSet.addLongIdentifier("pwCharacterSet", true);
910    generatedPasswordCharacterSet.addLongIdentifier("pw-character-set", true);
911    generatedPasswordCharacterSet.addLongIdentifier("passwordCharSet", true);
912    generatedPasswordCharacterSet.addLongIdentifier("password-charset", true);
913    generatedPasswordCharacterSet.addLongIdentifier("password-char-set", true);
914    generatedPasswordCharacterSet.addLongIdentifier("pwCharSet", true);
915    generatedPasswordCharacterSet.addLongIdentifier("pw-charset", true);
916    generatedPasswordCharacterSet.addLongIdentifier("pw-char-set", true);
917    generatedPasswordCharacterSet.addLongIdentifier("passwordChars", true);
918    generatedPasswordCharacterSet.addLongIdentifier("password-chars", true);
919    generatedPasswordCharacterSet.addLongIdentifier("pw-chars", true);
920    generatedPasswordCharacterSet.setArgumentGroupName(
921         INFO_PWMOD_ARG_GROUP_NEW_PASSWORD.get());
922    parser.addArgument(generatedPasswordCharacterSet);
923
924
925    // Current password arguments.
926    currentPassword = new StringArgument('c', "currentPassword", false, 1,
927         INFO_PWMOD_ARG_PLACEHOLDER_PASSWORD.get(),
928         INFO_PWMOD_ARG_DESC_CURRENT_PASSWORD.get());
929    currentPassword.addLongIdentifier("current-password", true);
930    currentPassword.addLongIdentifier("currentPW", true);
931    currentPassword.addLongIdentifier("current-pw", true);
932    currentPassword.addLongIdentifier("current", true);
933    currentPassword.addLongIdentifier("oldPassword", true);
934    currentPassword.addLongIdentifier("old-password", true);
935    currentPassword.addLongIdentifier("oldPW", true);
936    currentPassword.addLongIdentifier("old-pw", true);
937    currentPassword.addLongIdentifier("old", true);
938    currentPassword.setArgumentGroupName(
939         INFO_PWMOD_ARG_GROUP_CURRENT_PASSWORD.get());
940    parser.addArgument(currentPassword);
941
942    currentPasswordFile = new FileArgument('C', "currentPasswordFile", false, 1,
943         null, INFO_PWMOD_ARG_DESC_CURRENT_PASSWORD_FILE.get(), true, true,
944         true, false);
945    currentPasswordFile.addLongIdentifier("current-password-file", true);
946    currentPasswordFile.addLongIdentifier("currentPWFile", true);
947    currentPasswordFile.addLongIdentifier("current-pw-file", true);
948    currentPasswordFile.addLongIdentifier("currentFile", true);
949    currentPasswordFile.addLongIdentifier("current-file", true);
950    currentPasswordFile.addLongIdentifier("currentPasswordPath", true);
951    currentPasswordFile.addLongIdentifier("current-password-path", true);
952    currentPasswordFile.addLongIdentifier("currentPWPath", true);
953    currentPasswordFile.addLongIdentifier("current-pw-path", true);
954    currentPasswordFile.addLongIdentifier("currentPath", true);
955    currentPasswordFile.addLongIdentifier("current-path", true);
956    currentPasswordFile.addLongIdentifier("oldPasswordFile", true);
957    currentPasswordFile.addLongIdentifier("old-password-file", true);
958    currentPasswordFile.addLongIdentifier("oldPWFile", true);
959    currentPasswordFile.addLongIdentifier("old-pw-file", true);
960    currentPasswordFile.addLongIdentifier("oldFile", true);
961    currentPasswordFile.addLongIdentifier("old-file", true);
962    currentPasswordFile.addLongIdentifier("oldPasswordPath", true);
963    currentPasswordFile.addLongIdentifier("old-password-path", true);
964    currentPasswordFile.addLongIdentifier("oldPWPath", true);
965    currentPasswordFile.addLongIdentifier("old-pw-path", true);
966    currentPasswordFile.addLongIdentifier("oldPath", true);
967    currentPasswordFile.addLongIdentifier("old-path", true);
968    currentPasswordFile.setArgumentGroupName(
969         INFO_PWMOD_ARG_GROUP_CURRENT_PASSWORD.get());
970    parser.addArgument(currentPasswordFile);
971
972    promptForCurrentPassword = new BooleanArgument(null,
973         "promptForCurrentPassword", 1,
974         INFO_PWMOD_ARG_DESC_PROMPT_FOR_CURRENT_PASSWORD.get());
975    promptForCurrentPassword.addLongIdentifier("prompt-for-current-password",
976         true);
977    promptForCurrentPassword.addLongIdentifier("promptForCurrentPW", true);
978    promptForCurrentPassword.addLongIdentifier("prompt-for-current-pw", true);
979    promptForCurrentPassword.addLongIdentifier("promptForCurrent", true);
980    promptForCurrentPassword.addLongIdentifier("prompt-for-current", true);
981    promptForCurrentPassword.addLongIdentifier("promptCurrent", true);
982    promptForCurrentPassword.addLongIdentifier("prompt-current", true);
983    promptForCurrentPassword.addLongIdentifier("promptForOldPassword", true);
984    promptForCurrentPassword.addLongIdentifier("prompt-for-old-password", true);
985    promptForCurrentPassword.addLongIdentifier("promptForOldPW", true);
986    promptForCurrentPassword.addLongIdentifier("prompt-for-old-pw", true);
987    promptForCurrentPassword.addLongIdentifier("promptForOld", true);
988    promptForCurrentPassword.addLongIdentifier("prompt-for-old", true);
989    promptForCurrentPassword.addLongIdentifier("promptOld", true);
990    promptForCurrentPassword.addLongIdentifier("prompt-old", true);
991    promptForCurrentPassword.setArgumentGroupName(
992         INFO_PWMOD_ARG_GROUP_CURRENT_PASSWORD.get());
993    parser.addArgument(promptForCurrentPassword);
994
995
996    // Bind control arguments.
997    bindControl = new ControlArgument(null, "bindControl", false, 0, null,
998         INFO_PWMOD_ARG_DESC_BIND_CONTROL.get());
999    bindControl.addLongIdentifier("bind-control", true);
1000    bindControl.setArgumentGroupName(INFO_PWMOD_ARG_GROUP_BIND_CONTROL.get());
1001    parser.addArgument(bindControl);
1002
1003    useAuthorizationIdentityControl = new BooleanArgument(null,
1004         "useAuthorizationIdentityControl", 1,
1005         INFO_PWMOD_ARG_DESC_USE_AUTHZ_ID_CONTROL.get());
1006    useAuthorizationIdentityControl.addLongIdentifier(
1007         "use-authorization-identity-control", true);
1008    useAuthorizationIdentityControl.addLongIdentifier(
1009         "useAuthorizationID-control", true);
1010    useAuthorizationIdentityControl.addLongIdentifier(
1011         "use-authorization-id-control", true);
1012    useAuthorizationIdentityControl.addLongIdentifier(
1013         "authorizationIdentityControl", true);
1014    useAuthorizationIdentityControl.addLongIdentifier(
1015         "authorization-identity-control", true);
1016    useAuthorizationIdentityControl.addLongIdentifier("authorizationIDControl",
1017         true);
1018    useAuthorizationIdentityControl.addLongIdentifier(
1019         "authorization-id-control", true);
1020    useAuthorizationIdentityControl.addLongIdentifier("authzIDControl", true);
1021    useAuthorizationIdentityControl.addLongIdentifier("authz-id-control", true);
1022    useAuthorizationIdentityControl.setArgumentGroupName(
1023         INFO_PWMOD_ARG_GROUP_BIND_CONTROL.get());
1024    parser.addArgument(useAuthorizationIdentityControl);
1025
1026    usePasswordPolicyControlOnBind = new BooleanArgument(null,
1027         "usePasswordPolicyControlOnBind", 1,
1028         INFO_PWMOD_ARG_DESC_USE_PW_POLICY_CONTROL_ON_BIND.get());
1029    usePasswordPolicyControlOnBind.addLongIdentifier(
1030         "use-password-policy-control-on-bind", true);
1031    usePasswordPolicyControlOnBind.addLongIdentifier("usePWPolicyControlOnBind",
1032         true);
1033    usePasswordPolicyControlOnBind.addLongIdentifier(
1034         "use-pw-policy-control-on-bind", true);
1035    usePasswordPolicyControlOnBind.setArgumentGroupName(
1036         INFO_PWMOD_ARG_GROUP_BIND_CONTROL.get());
1037    parser.addArgument(usePasswordPolicyControlOnBind);
1038
1039    getAuthorizationEntryAttribute = new StringArgument(null,
1040         "getAuthorizationEntryAttribute", false, 0,
1041         INFO_PWMOD_ARG_PLACEHOLDER_ATTRIBUTE_NAME.get(),
1042         INFO_PWMOD_ARG_DESC_GET_AUTHZ_ENTRY_ATTRIBUTE.get());
1043    getAuthorizationEntryAttribute.addLongIdentifier(
1044         "get-authorization-entry-attribute", true);
1045    getAuthorizationEntryAttribute.setArgumentGroupName(
1046         INFO_PWMOD_ARG_GROUP_BIND_CONTROL.get());
1047    parser.addArgument(getAuthorizationEntryAttribute);
1048
1049    getUserResourceLimits = new BooleanArgument(null, "getUserResourceLimits",
1050         1, INFO_PWMOD_ARG_DESC_GET_USER_RESOURCE_LIMITS.get());
1051    getUserResourceLimits.addLongIdentifier("get-user-resource-limits", true);
1052    getUserResourceLimits.setArgumentGroupName(
1053         INFO_PWMOD_ARG_GROUP_BIND_CONTROL.get());
1054    parser.addArgument(getUserResourceLimits);
1055
1056
1057    // Update control arguments.
1058    updateControl = new ControlArgument('J', "updateControl", false, 0, null,
1059         INFO_PWMOD_ARG_DESC_UPDATE_CONTROL.get());
1060    updateControl.addLongIdentifier("update-control", true);
1061    updateControl.addLongIdentifier("control", true);
1062    updateControl.setArgumentGroupName(
1063         INFO_PWMOD_ARG_GROUP_UPDATE_CONTROL.get());
1064    parser.addArgument(updateControl);
1065
1066    usePasswordPolicyControlOnUpdate = new BooleanArgument(null,
1067         "usePasswordPolicyControlOnUpdate", 1,
1068         INFO_PWMOD_ARG_DESC_USE_PW_POLICY_CONTROL_ON_UPDATE.get());
1069    usePasswordPolicyControlOnUpdate.addLongIdentifier(
1070         "use-password-policy-control-on-update", true);
1071    usePasswordPolicyControlOnUpdate.addLongIdentifier(
1072         "usePWPolicyControlOnUpdate", true);
1073    usePasswordPolicyControlOnUpdate.addLongIdentifier(
1074         "use-pw-policy-control-on-update", true);
1075    usePasswordPolicyControlOnUpdate.setArgumentGroupName(
1076         INFO_PWMOD_ARG_GROUP_UPDATE_CONTROL.get());
1077    parser.addArgument(usePasswordPolicyControlOnUpdate);
1078
1079    noOperation = new BooleanArgument(null, "noOperation", 1,
1080         INFO_PWMOD_ARG_DESC_NO_OPERATION.get());
1081    noOperation.addLongIdentifier("no-operation", true);
1082    noOperation.addLongIdentifier("noOp", true);
1083    noOperation.addLongIdentifier("no-op", true);
1084    noOperation.setArgumentGroupName(INFO_PWMOD_ARG_GROUP_UPDATE_CONTROL.get());
1085    parser.addArgument(noOperation);
1086
1087    getPasswordValidationDetails = new BooleanArgument(null,
1088         "getPasswordValidationDetails", 1,
1089         INFO_PWMOD_ARG_DESC_GET_PW_VALIDATION_DETAILS.get());
1090    getPasswordValidationDetails.addLongIdentifier(
1091         "get-password-validation-details", true);
1092    getPasswordValidationDetails.addLongIdentifier("getPWValidationDetails",
1093         true);
1094    getPasswordValidationDetails.addLongIdentifier("get-pw-validation-details",
1095         true);
1096    getPasswordValidationDetails.setArgumentGroupName(
1097         INFO_PWMOD_ARG_GROUP_UPDATE_CONTROL.get());
1098    parser.addArgument(getPasswordValidationDetails);
1099
1100    retireCurrentPassword = new BooleanArgument(null, "retireCurrentPassword",
1101         1, INFO_PWMOD_ARG_DESC_RETIRE_CURRENT_PASSWORD.get());
1102    retireCurrentPassword.addLongIdentifier("retire-current-password", true);
1103    retireCurrentPassword.addLongIdentifier("retireCurrentPW", true);
1104    retireCurrentPassword.addLongIdentifier("retire-current-pw", true);
1105    retireCurrentPassword.addLongIdentifier("retirePassword", true);
1106    retireCurrentPassword.addLongIdentifier("retire-password", true);
1107    retireCurrentPassword.addLongIdentifier("retirePW", true);
1108    retireCurrentPassword.addLongIdentifier("retire-pw", true);
1109    retireCurrentPassword.setArgumentGroupName(
1110         INFO_PWMOD_ARG_GROUP_UPDATE_CONTROL.get());
1111    parser.addArgument(retireCurrentPassword);
1112
1113    purgeCurrentPassword = new BooleanArgument(null, "purgeCurrentPassword", 1,
1114         INFO_PWMOD_ARG_DESC_PURGE_CURRENT_PASSWORD.get());
1115    purgeCurrentPassword.addLongIdentifier("purge-current-password", true);
1116    purgeCurrentPassword.addLongIdentifier("purgeCurrentPW", true);
1117    purgeCurrentPassword.addLongIdentifier("purge-current-pw", true);
1118    purgeCurrentPassword.addLongIdentifier("purgePassword", true);
1119    purgeCurrentPassword.addLongIdentifier("purge-password", true);
1120    purgeCurrentPassword.addLongIdentifier("purgePW", true);
1121    purgeCurrentPassword.addLongIdentifier("purge-pw", true);
1122    purgeCurrentPassword.setArgumentGroupName(
1123         INFO_PWMOD_ARG_GROUP_UPDATE_CONTROL.get());
1124    parser.addArgument(purgeCurrentPassword);
1125
1126    passwordUpdateBehavior = new StringArgument(null,
1127         "passwordUpdateBehavior", false, 0,
1128         INFO_PWMOD_ARG_PLACEHOLDER_NAME_VALUE.get(),
1129         INFO_PWMOD_ARG_DESC_PASSWORD_UPDATE_BEHAVIOR.get());
1130    passwordUpdateBehavior.addLongIdentifier("password-update-behavior", true);
1131    passwordUpdateBehavior.addLongIdentifier("pwUpdateBehavior", true);
1132    passwordUpdateBehavior.addLongIdentifier("pw-update-behavior", true);
1133    passwordUpdateBehavior.addLongIdentifier("updateBehavior", true);
1134    passwordUpdateBehavior.addLongIdentifier("update-behavior", true);
1135    passwordUpdateBehavior.setArgumentGroupName(
1136         INFO_PWMOD_ARG_GROUP_UPDATE_CONTROL.get());
1137    parser.addArgument(passwordUpdateBehavior);
1138
1139    useAssuredReplication = new BooleanArgument(null, "useAssuredReplication",
1140         1, INFO_PWMOD_ARG_DESC_ASSURED_REPLICATION.get());
1141    useAssuredReplication.addLongIdentifier("use-assured-replication", true);
1142    useAssuredReplication.addLongIdentifier("assuredReplication", true);
1143    useAssuredReplication.addLongIdentifier("assured-replication", true);
1144    useAssuredReplication.setArgumentGroupName(
1145         INFO_PWMOD_ARG_GROUP_UPDATE_CONTROL.get());
1146    parser.addArgument(useAssuredReplication);
1147
1148    assuredReplicationLocalLevel = new StringArgument(null,
1149         "assuredReplicationLocalLevel", false, 1,
1150         INFO_PWMOD_ARG_PLACEHOLDER_LEVEL.get(),
1151         INFO_PWMOD_ARG_DESC_ASSURED_REPLICATION_LOCAL_LEVEL.get(),
1152         StaticUtils.setOf(
1153              ASSURED_REPLICATION_LOCAL_LEVEL_NONE,
1154              ASSURED_REPLICATION_LOCAL_LEVEL_RECEIVED_ANY_SERVER,
1155              ASSURED_REPLICATION_LOCAL_LEVEL_PROCESSED_ALL_SERVERS));
1156    assuredReplicationLocalLevel.addLongIdentifier(
1157         "assured-replication-local-level", true);
1158    assuredReplicationLocalLevel.addLongIdentifier("localLevel", true);
1159    assuredReplicationLocalLevel.addLongIdentifier("local-level", true);
1160    assuredReplicationLocalLevel.setArgumentGroupName(
1161         INFO_PWMOD_ARG_GROUP_UPDATE_CONTROL.get());
1162    parser.addArgument(assuredReplicationLocalLevel);
1163
1164    assuredReplicationRemoteLevel = new StringArgument(null,
1165         "assuredReplicationRemoteLevel", false, 1,
1166         INFO_PWMOD_ARG_PLACEHOLDER_LEVEL.get(),
1167         INFO_PWMOD_ARG_DESC_ASSURED_REPLICATION_REMOTE_LEVEL.get(),
1168         StaticUtils.setOf(
1169              ASSURED_REPLICATION_REMOTE_LEVEL_NONE,
1170              ASSURED_REPLICATION_REMOTE_LEVEL_RECEIVED_ANY_REMOTE_LOCATION,
1171              ASSURED_REPLICATION_REMOTE_LEVEL_RECEIVED_ALL_REMOTE_LOCATIONS,
1172              ASSURED_REPLICATION_REMOTE_LEVEL_PROCESSED_ALL_REMOTE_SERVERS));
1173    assuredReplicationRemoteLevel.addLongIdentifier(
1174         "assured-replication-remote-level", true);
1175    assuredReplicationRemoteLevel.addLongIdentifier("remoteLevel", true);
1176    assuredReplicationRemoteLevel.addLongIdentifier("remote-level", true);
1177    assuredReplicationRemoteLevel.setArgumentGroupName(
1178         INFO_PWMOD_ARG_GROUP_UPDATE_CONTROL.get());
1179    parser.addArgument(assuredReplicationRemoteLevel);
1180
1181    assuredReplicationTimeout = new DurationArgument(null,
1182         "assuredReplicationTimeout", false,
1183         INFO_PWMOD_ARG_PLACEHOLDER_TIMEOUT.get(),
1184         INFO_PWMOD_ARG_DESC_ASSURED_REPLICATION_TIMEOUT.get());
1185    assuredReplicationTimeout.addLongIdentifier("assured-replication-timeout",
1186         true);
1187    assuredReplicationTimeout.setArgumentGroupName(
1188         INFO_PWMOD_ARG_GROUP_UPDATE_CONTROL.get());
1189    parser.addArgument(assuredReplicationTimeout);
1190
1191    operationPurpose = new StringArgument(null, "operationPurpose", false, 1,
1192         INFO_PWMOD_ARG_PLACEHOLDER_PURPOSE.get(),
1193         INFO_PWMOD_ARG_DESC_OPERATION_PURPOSE.get());
1194    operationPurpose.addLongIdentifier("operation-purpose", true);
1195    operationPurpose.setArgumentGroupName(
1196         INFO_PWMOD_ARG_GROUP_UPDATE_CONTROL.get());
1197    parser.addArgument(operationPurpose);
1198
1199
1200    // Other arguments
1201    passwordAttribute = new StringArgument(null, "passwordAttribute", false, 1,
1202         INFO_PWMOD_ARG_PLACEHOLDER_ATTRIBUTE_NAME.get(),
1203         INFO_PWMOD_ARG_DESC_PASSWORD_ATTRIBUTE.get(),
1204         DEFAULT_PASSWORD_ATTRIBUTE);
1205    passwordAttribute.addLongIdentifier("password-attribute", true);
1206    passwordAttribute.addLongIdentifier("passwordAttr", true);
1207    passwordAttribute.addLongIdentifier("password-attr", true);
1208    passwordAttribute.addLongIdentifier("pwAttribute", true);
1209    passwordAttribute.addLongIdentifier("pw-attribute", true);
1210    passwordAttribute.addLongIdentifier("pwAttr", true);
1211    passwordAttribute.addLongIdentifier("pw-attr", true);
1212    passwordAttribute.setArgumentGroupName(
1213         INFO_PWMOD_ARG_GROUP_OTHER.get());
1214
1215    passwordChangeMethod = new StringArgument(null, "passwordChangeMethod",
1216         false, 1, INFO_PWMOD_ARG_PLACEHOLDER_CHANGE_METHOD.get(),
1217         INFO_PWMOD_ARG_DESC_PASSWORD_CHANGE_METHOD.get(),
1218         StaticUtils.setOf(
1219              PASSWORD_CHANGE_METHOD_PW_MOD_EXTOP,
1220              PASSWORD_CHANGE_METHOD_LDAP_MOD,
1221              PASSWORD_CHANGE_METHOD_AD));
1222    passwordChangeMethod.addLongIdentifier("password-change-method", true);
1223    passwordChangeMethod.addLongIdentifier("pwChangeMethod", true);
1224    passwordChangeMethod.addLongIdentifier("pw-change-method", true);
1225    passwordChangeMethod.addLongIdentifier("changeMethod", true);
1226    passwordChangeMethod.addLongIdentifier("change-method", true);
1227    passwordChangeMethod.addLongIdentifier("method", true);
1228    passwordChangeMethod.setArgumentGroupName(
1229         INFO_PWMOD_ARG_GROUP_OTHER.get());
1230    parser.addArgument(passwordChangeMethod);
1231
1232    followReferrals = new BooleanArgument(null, "followReferrals", 1,
1233         INFO_PWMOD_ARG_DESC_FOLLOW_REFERRALS.get());
1234    followReferrals.addLongIdentifier("follow-referrals", true);
1235    followReferrals.setArgumentGroupName(
1236         INFO_PWMOD_ARG_GROUP_OTHER.get());
1237    parser.addArgument(followReferrals);
1238
1239    useAdministrativeSession = new BooleanArgument(null,
1240         "useAdministrativeSession", 1,
1241         INFO_PWMOD_ARG_DESC_USE_ADMIN_SESSION.get());
1242    useAdministrativeSession.addLongIdentifier("use-administrative-session",
1243         true);
1244    useAdministrativeSession.addLongIdentifier("useAdminSession", true);
1245    useAdministrativeSession.addLongIdentifier("use-admin-session", true);
1246    useAdministrativeSession.addLongIdentifier("administrativeSession", true);
1247    useAdministrativeSession.addLongIdentifier("administrative-session", true);
1248    useAdministrativeSession.addLongIdentifier("adminSession", true);
1249    useAdministrativeSession.addLongIdentifier("admin-session", true);
1250    useAdministrativeSession.setArgumentGroupName(
1251         INFO_PWMOD_ARG_GROUP_OTHER.get());
1252    parser.addArgument(useAdministrativeSession);
1253
1254    verbose = new BooleanArgument('v', "verbose", 1,
1255         INFO_PWMOD_ARG_DESC_VERBOSE.get());
1256    verbose.setArgumentGroupName(INFO_PWMOD_ARG_GROUP_OTHER.get());
1257    parser.addArgument(verbose);
1258
1259    // This argument isn't actually used, but provides command-line backward
1260    // compatibility with an existing implementation.
1261    scriptFriendly = new BooleanArgument(null, "script-friendly", 1,
1262         INFO_PWMOD_ARG_DESC_SCRIPT_FRIENDLY.get());
1263    scriptFriendly.setArgumentGroupName(INFO_PWMOD_ARG_GROUP_OTHER.get());
1264    scriptFriendly.setHidden(true);
1265    parser.addArgument(scriptFriendly);
1266
1267
1268    // Argument constraints.
1269    parser.addExclusiveArgumentSet(userIdentity, provideBindDNAsUserIdentity);
1270
1271    final DNArgument bindDNArgument =
1272         parser.getDNArgument(BIND_DN_ARGUMENT_LONG_IDENTIFIER);
1273    parser.addDependentArgumentSet(provideBindDNAsUserIdentity, bindDNArgument);
1274
1275    parser.addExclusiveArgumentSet(newPassword, newPasswordFile,
1276         promptForNewPassword, generateClientSideNewPassword);
1277
1278    parser.addDependentArgumentSet(generatedPasswordLength,
1279         generateClientSideNewPassword);
1280    parser.addDependentArgumentSet(generatedPasswordCharacterSet,
1281         generateClientSideNewPassword);
1282
1283    parser.addExclusiveArgumentSet(currentPassword, currentPasswordFile,
1284         promptForCurrentPassword);
1285
1286    parser.addDependentArgumentSet(assuredReplicationLocalLevel,
1287         useAssuredReplication);
1288    parser.addDependentArgumentSet(assuredReplicationRemoteLevel,
1289         useAssuredReplication);
1290    parser.addDependentArgumentSet(assuredReplicationTimeout,
1291         useAssuredReplication);
1292
1293    parser.addExclusiveArgumentSet(retireCurrentPassword, purgeCurrentPassword);
1294  }
1295
1296
1297
1298  /**
1299   * {@inheritDoc}
1300   */
1301  @Override()
1302  @NotNull()
1303  protected Set<Character> getSuppressedShortIdentifiers()
1304  {
1305    return StaticUtils.setOf('N');
1306  }
1307
1308
1309
1310  /**
1311   * {@inheritDoc}
1312   */
1313  @Override()
1314  public void doExtendedNonLDAPArgumentValidation()
1315         throws ArgumentException
1316  {
1317    // Make sure that if any generate password character sets were provided,
1318    // they must all be non-empty.
1319    if (generatedPasswordCharacterSet.isPresent())
1320    {
1321      for (final String charSet : generatedPasswordCharacterSet.getValues())
1322      {
1323        if (charSet.isEmpty())
1324        {
1325          throw new ArgumentException(ERR_PWMOD_CHAR_SET_EMPTY.get(
1326               generatedPasswordCharacterSet.getIdentifierString()));
1327        }
1328      }
1329    }
1330  }
1331
1332
1333
1334  /**
1335   * {@inheritDoc}
1336   */
1337  @Override()
1338  @NotNull()
1339  public ResultCode doToolProcessing()
1340  {
1341    LDAPConnectionPool pool = null;
1342    try
1343    {
1344      // Create a connection pool that will be used to communicate with the
1345      // directory server.  If we should use an administrative session, then
1346      // create a connect processor that will be used to start the session
1347      // before performing the bind.
1348      try
1349      {
1350        final StartAdministrativeSessionPostConnectProcessor p;
1351        if (useAdministrativeSession.isPresent())
1352        {
1353          p = new StartAdministrativeSessionPostConnectProcessor(
1354               new StartAdministrativeSessionExtendedRequest(getToolName(),
1355                    true));
1356        }
1357        else
1358        {
1359          p = null;
1360        }
1361
1362        pool = getConnectionPool(1, 2, 0, p, null, true,
1363             new ReportBindResultLDAPConnectionPoolHealthCheck(this, true,
1364                  verbose.isPresent()));
1365
1366
1367        // Figure out the method to use to update the password.
1368        final String updateMethod;
1369        try
1370        {
1371          updateMethod = getPasswordUpdateMethod(pool);
1372        }
1373        catch (final LDAPException e)
1374        {
1375          Debug.debugException(e);
1376          logCompletionMessage(true, e.getMessage());
1377          return e.getResultCode();
1378        }
1379
1380
1381        switch (updateMethod)
1382        {
1383          case PASSWORD_CHANGE_METHOD_PW_MOD_EXTOP:
1384            return doPasswordModifyExtendedOperation(pool);
1385
1386          case PASSWORD_CHANGE_METHOD_AD:
1387            return doLDAPModifyPasswordUpdate(pool, true);
1388
1389          case PASSWORD_CHANGE_METHOD_LDAP_MOD:
1390          default:
1391            return doLDAPModifyPasswordUpdate(pool, false);
1392        }
1393      }
1394      catch (final LDAPException le)
1395      {
1396        Debug.debugException(le);
1397
1398        // Unable to create the connection pool, which means that either the
1399        // connection could not be established or the attempt to authenticate
1400        // the connection failed.  If the bind failed, then the report bind
1401        // result health check should have already reported the bind failure.
1402        // If the failure was something else, then display that failure result.
1403        if (le.getResultCode() != ResultCode.INVALID_CREDENTIALS)
1404        {
1405          for (final String line :
1406               ResultUtils.formatResult(le, true, 0, WRAP_COLUMN))
1407          {
1408            err(line);
1409          }
1410        }
1411        return le.getResultCode();
1412      }
1413    }
1414    finally
1415    {
1416      if (pool != null)
1417      {
1418        pool.close();
1419      }
1420    }
1421  }
1422
1423
1424
1425  /**
1426   * Determines the method that should be used to update the password.
1427   *
1428   * @param  pool  The connection pool to use to communicate with the
1429   *               directory server, if appropriate.
1430   *
1431   * @return  The method that should be used to update the password.  The value
1432   *          returned will be one of
1433   *          {@link #PASSWORD_CHANGE_METHOD_PW_MOD_EXTOP},
1434   *          {@link #PASSWORD_CHANGE_METHOD_LDAP_MOD}, or
1435   *          {@link #PASSWORD_CHANGE_METHOD_AD}.
1436   *
1437   * @throws  LDAPException  If a problem occurs while attempting to make the
1438   *                         determination.
1439   */
1440  @NotNull()
1441  private String getPasswordUpdateMethod(@NotNull final LDAPConnectionPool pool)
1442          throws LDAPException
1443  {
1444    if (passwordChangeMethod.isPresent())
1445    {
1446      switch (StaticUtils.toLowerCase(passwordChangeMethod.getValue()))
1447      {
1448        case PASSWORD_CHANGE_METHOD_PW_MOD_EXTOP:
1449          return PASSWORD_CHANGE_METHOD_PW_MOD_EXTOP;
1450        case PASSWORD_CHANGE_METHOD_LDAP_MOD:
1451          return PASSWORD_CHANGE_METHOD_LDAP_MOD;
1452        case PASSWORD_CHANGE_METHOD_AD:
1453          return PASSWORD_CHANGE_METHOD_AD;
1454      }
1455    }
1456
1457
1458    // Retrieve the root DSE from the directory server.  If we can't get the
1459    // root DSE, then default to the password modify extended operation.
1460    final RootDSE rootDSE;
1461    try
1462    {
1463      rootDSE = pool.getRootDSE();
1464    }
1465    catch (final LDAPException e)
1466    {
1467      Debug.debugException(e);
1468      return PASSWORD_CHANGE_METHOD_PW_MOD_EXTOP;
1469    }
1470
1471    if (rootDSE == null)
1472    {
1473      return PASSWORD_CHANGE_METHOD_PW_MOD_EXTOP;
1474    }
1475
1476
1477    // If the root DSE claims support for the password modify extended
1478    // operation, then use that method.
1479    if (rootDSE.supportsExtendedOperation(
1480         PasswordModifyExtendedRequest.PASSWORD_MODIFY_REQUEST_OID))
1481    {
1482      if (verbose.isPresent())
1483      {
1484        wrapOut(0, WRAP_COLUMN,
1485             INFO_PWMOD_SELECTING_PW_MOD_EXTOP_METHOD.get());
1486      }
1487
1488      return PASSWORD_CHANGE_METHOD_PW_MOD_EXTOP;
1489    }
1490
1491
1492    // We need to differentiate between Active Directory and other types of
1493    // servers.  Unfortunately, Active Directory doesn't seem to provide
1494    // vendorName or vendorVersion attributes in its root DSE, so we'll need to
1495    // use some other means of detecting it.  Let's assume that if the server
1496    // advertises support for at least twenty supported controls in Microsoft's
1497    // OID range (starting with 1.2.840.113556), then it's an Active Directory
1498    // instance.  At the time this was written, two different AD versions each
1499    // advertised support for nearly double that number.
1500    int numMicrosoftControlsSupported = 0;
1501    for (final String oid : rootDSE.getSupportedControlOIDs())
1502    {
1503      if (oid.startsWith(MICROSOFT_BASE_OBJECT_IDENTIFIER + '.'))
1504      {
1505        numMicrosoftControlsSupported++;
1506      }
1507    }
1508
1509    if (numMicrosoftControlsSupported >= 20)
1510    {
1511      if (verbose.isPresent())
1512      {
1513        wrapOut(0, WRAP_COLUMN,
1514             INFO_PWMOD_SELECTING_AD_METHOD_CONTROL_COUNT.get(
1515                  numMicrosoftControlsSupported,
1516                  MICROSOFT_BASE_OBJECT_IDENTIFIER));
1517      }
1518
1519      return PASSWORD_CHANGE_METHOD_AD;
1520    }
1521
1522
1523    // Fall back to a default of a regular LDAP modify operation.
1524    if (verbose.isPresent())
1525    {
1526      wrapOut(0, WRAP_COLUMN,
1527           INFO_PWMOD_DEFAULTING_TO_LDAP_MOD.get());
1528    }
1529
1530    return PASSWORD_CHANGE_METHOD_LDAP_MOD;
1531  }
1532
1533
1534
1535  /**
1536   * Attempts a password modify extended operation to change the target user's
1537   * password.
1538   *
1539   * @param  pool  A connection pool to use to communicate with the directory
1540   *               server.
1541   *
1542   * @return  A result code that indicates whether the password update was
1543   *          successful.
1544   */
1545  @NotNull()
1546  private ResultCode doPasswordModifyExtendedOperation(
1547                          @NotNull final LDAPConnectionPool pool)
1548  {
1549    // Create the password modify extended request to be processed.
1550    final String identity;
1551    final byte[] currentPW;
1552    final byte[] newPW;
1553    final Control[] controls;
1554    try
1555    {
1556      identity = getUserIdentity(null, false);
1557      currentPW = getCurrentPassword();
1558      newPW = getNewPassword();
1559      controls = getUpdateControls();
1560    }
1561    catch (final LDAPException e)
1562    {
1563      Debug.debugException(e);
1564      logCompletionMessage(true, e.getMessage());
1565      return e.getResultCode();
1566    }
1567
1568    final PasswordModifyExtendedRequest passwordModifyRequest =
1569         new PasswordModifyExtendedRequest(identity, currentPW, newPW,
1570              controls);
1571
1572
1573    // Send the request and interpret the response.
1574    LDAPConnection connection = null;
1575    try
1576    {
1577      connection = pool.getConnection();
1578      final PasswordModifyExtendedResult passwordModifyResult =
1579           (PasswordModifyExtendedResult)
1580           connection.processExtendedOperation(passwordModifyRequest);
1581
1582      out();
1583      out(INFO_PWMOD_EXTOP_RESULT_HEADER.get());
1584      for (final String line :
1585           ResultUtils.formatResult(passwordModifyResult, true, 0, WRAP_COLUMN))
1586      {
1587        out(line);
1588      }
1589      out();
1590
1591      final String generatedPassword =
1592           passwordModifyResult.getGeneratedPassword();
1593      if (passwordModifyResult.getResultCode() == ResultCode.SUCCESS)
1594      {
1595        logCompletionMessage(false, INFO_PWMOD_EXTOP_SUCCESSFUL.get());
1596        if (generatedPassword != null)
1597        {
1598          out();
1599          wrapOut(0, WRAP_COLUMN,
1600               INFO_PWMOD_SERVER_GENERATED_PW.get(generatedPassword));
1601        }
1602
1603        return ResultCode.SUCCESS;
1604      }
1605      else if (passwordModifyResult.getResultCode() == ResultCode.NO_OPERATION)
1606      {
1607        logCompletionMessage(false, INFO_PWMOD_EXTOP_NO_OP.get());
1608        if (generatedPassword != null)
1609        {
1610          out();
1611          wrapOut(0, WRAP_COLUMN,
1612               INFO_PWMOD_SERVER_GENERATED_PW.get(generatedPassword));
1613        }
1614
1615        return ResultCode.SUCCESS;
1616      }
1617      else
1618      {
1619        logCompletionMessage(true,
1620             ERR_PWMOD_EXTOP_FAILED.get(
1621                  String.valueOf(passwordModifyResult.getResultCode()),
1622                  passwordModifyResult.getDiagnosticMessage()));
1623        return passwordModifyResult.getResultCode();
1624      }
1625    }
1626    catch (final LDAPException e)
1627    {
1628      Debug.debugException(e);
1629
1630      err();
1631      err(INFO_PWMOD_EXTOP_RESULT_HEADER.get());
1632      for (final String line :
1633           ResultUtils.formatResult(e, true, 0, WRAP_COLUMN))
1634      {
1635        err(line);
1636      }
1637      err();
1638
1639      if (connection != null)
1640      {
1641        pool.releaseDefunctConnection(connection);
1642        connection = null;
1643      }
1644
1645      logCompletionMessage(true,
1646           ERR_PWMOD_EXTOP_ERROR.get(String.valueOf(e.getResultCode()),
1647                e.getMessage()));
1648      return e.getResultCode();
1649    }
1650    finally
1651    {
1652      if (connection != null)
1653      {
1654        pool.releaseConnection(connection);
1655      }
1656    }
1657  }
1658
1659
1660
1661  /**
1662   * Attempts a regular LDAP modify operation to change the target user's
1663   * password.
1664   *
1665   * @param  pool               A connection pool to use to communicate with the
1666   *                            directory server.
1667   * @param  isActiveDirectory  Indicates whether the target directory server
1668   *                            is believed to be an Active Directory instance.
1669   *
1670   * @return  A result code that indicates whether the password update was
1671   *          successful.
1672   */
1673  @NotNull()
1674  private ResultCode doLDAPModifyPasswordUpdate(
1675               @NotNull final LDAPConnectionPool pool,
1676               final boolean isActiveDirectory)
1677  {
1678    // Get the information to include in the password modify extended request.
1679    byte[] currentPW;
1680    byte[] newPW;
1681    final String identity;
1682    final Control[] controls;
1683    try
1684    {
1685      identity = getUserIdentity(pool, isActiveDirectory);
1686      currentPW = getCurrentPassword();
1687      newPW = getNewPassword();
1688      controls = getUpdateControls();
1689    }
1690    catch (final LDAPException e)
1691    {
1692      Debug.debugException(e);
1693      logCompletionMessage(true, e.getMessage());
1694      return e.getResultCode();
1695    }
1696
1697
1698    // If there is no new password, then fail.
1699    if (newPW == null)
1700    {
1701      logCompletionMessage(true,
1702           ERR_PWMOD_NO_NEW_PW_FOR_MODIFY.get(newPassword.getIdentifierString(),
1703                newPasswordFile.getIdentifierString(),
1704                promptForNewPassword.getIdentifierString(),
1705                generateClientSideNewPassword.getIdentifierString()));
1706      return ResultCode.PARAM_ERROR;
1707    }
1708
1709
1710    // Determine the name of the attribute to modify.
1711    final String passwordAttr;
1712    if (isActiveDirectory)
1713    {
1714      passwordAttr = AD_PASSWORD_ATTRIBUTE;
1715      currentPW = encodePasswordForActiveDirectory(currentPW);
1716      newPW = encodePasswordForActiveDirectory(newPW);
1717    }
1718    else
1719    {
1720      passwordAttr = passwordAttribute.getValue();
1721    }
1722
1723
1724    // Construct the modify request to send to the server.
1725    final ModifyRequest modifyRequest;
1726    if (currentPW == null)
1727    {
1728      modifyRequest = new ModifyRequest(identity,
1729           new Modification(ModificationType.REPLACE, passwordAttr, newPW));
1730    }
1731    else
1732    {
1733      modifyRequest = new ModifyRequest(identity,
1734           new Modification(ModificationType.DELETE, passwordAttr, currentPW),
1735           new Modification(ModificationType.ADD, passwordAttr, newPW));
1736    }
1737
1738    modifyRequest.setControls(controls);
1739
1740
1741    // Send the modify request and read the result.
1742    LDAPResult modifyResult;
1743    try
1744    {
1745      modifyResult = pool.modify(modifyRequest);
1746    }
1747    catch (final LDAPException e)
1748    {
1749      Debug.debugException(e);
1750      modifyResult = e.toLDAPResult();
1751    }
1752
1753
1754    out();
1755    out(INFO_PWMOD_MODIFY_RESULT_HEADER.get());
1756    for (final String line :
1757         ResultUtils.formatResult(modifyResult, true, 0, WRAP_COLUMN))
1758    {
1759      out(line);
1760    }
1761    out();
1762
1763    if (modifyResult.getResultCode() == ResultCode.SUCCESS)
1764    {
1765      logCompletionMessage(false, INFO_PWMOD_MODIFY_SUCCESSFUL.get());
1766      return ResultCode.SUCCESS;
1767    }
1768    else if (modifyResult.getResultCode() == ResultCode.NO_OPERATION)
1769    {
1770      logCompletionMessage(false, INFO_PWMOD_MODIFY_NO_OP.get());
1771      return ResultCode.SUCCESS;
1772    }
1773    else
1774    {
1775      logCompletionMessage(true,
1776           ERR_PWMOD_MODIFY_FAILED.get(
1777                String.valueOf(modifyResult.getResultCode()),
1778                modifyResult.getDiagnosticMessage()));
1779      return modifyResult.getResultCode();
1780    }
1781  }
1782
1783
1784
1785  /**
1786   *  Encodes the provided password in the form that is needed when changing a
1787   *  password in Active Directory.  The password must be surrounded in
1788   *  quotation marks and encoded as UTF-16 with little-Endian ordering.
1789   *
1790   * @param  pw  The password to be encoded.  It may optionally be {@code null}.
1791   *
1792   * @return  The encoded password.
1793   */
1794  @Nullable()
1795  static byte[] encodePasswordForActiveDirectory(@Nullable final byte[] pw)
1796  {
1797    if (pw == null)
1798    {
1799      return null;
1800    }
1801
1802    final String quotedPassword = '"' + StaticUtils.toUTF8String(pw) + '"';
1803    return quotedPassword.getBytes(StandardCharsets.UTF_16LE);
1804  }
1805
1806
1807
1808  /**
1809   * Retrieves the user identity for whom to update the password.
1810   *
1811   * @param  pool               A connection pool to use to communicate with the
1812   *                            directory server, if necessary.  This may be
1813   *                            {@code null} if only an explicitly provided user
1814   *                            identity should be used.  If it is
1815   *                            non-{@code null}, then an attempt will be made
1816   *                            to infer the correct value, and the value
1817   *                            returned will be a DN.
1818   * @param  isActiveDirectory  Indicates whether the target directory server
1819   *                            is believed to be an Active Directory instance.
1820   *
1821   * @return  The user identity for whom to update the password.
1822   *
1823   * @throws  LDAPException  If a problem occurs while attempting to obtain the
1824   *                         user identity.
1825   */
1826  @NotNull()
1827  private String getUserIdentity(@NotNull final LDAPConnectionPool pool,
1828                                 final boolean isActiveDirectory)
1829          throws LDAPException
1830  {
1831    String identity = null;
1832    final DNArgument bindDNArgument =
1833         argumentParser.getDNArgument(BIND_DN_ARGUMENT_LONG_IDENTIFIER);
1834    if (userIdentity.isPresent())
1835    {
1836      identity = userIdentity.getValue();
1837    }
1838    else if (provideBindDNAsUserIdentity.isPresent())
1839    {
1840      identity = bindDNArgument.getStringValue();
1841      if ((pool == null) && verbose.isPresent())
1842      {
1843        out();
1844        wrapOut(0, WRAP_COLUMN,
1845             INFO_PWMOD_USING_USER_IDENTITY_FROM_DN_FOR_EXTOP.get(identity));
1846      }
1847    }
1848    else
1849    {
1850      if ((pool == null) && verbose.isPresent())
1851      {
1852        out();
1853        wrapOut(0, WRAP_COLUMN,
1854             INFO_PWMOD_OMITTING_USER_IDENTITY_FROM_EXTOP.get());
1855      }
1856    }
1857
1858    if (pool == null)
1859    {
1860      return identity;
1861    }
1862
1863    if (identity == null)
1864    {
1865      if (bindDNArgument.isPresent())
1866      {
1867        final DN bindDN = bindDNArgument.getValue();
1868        if (! bindDN.isNullDN())
1869        {
1870          return bindDN.toString();
1871        }
1872      }
1873
1874      final WhoAmIExtendedRequest whoAmIRequest = new WhoAmIExtendedRequest();
1875      try
1876      {
1877        final WhoAmIExtendedResult whoAmIResult = (WhoAmIExtendedResult)
1878             pool.processExtendedOperation(whoAmIRequest);
1879        if (whoAmIResult.getResultCode() == ResultCode.SUCCESS)
1880        {
1881          identity = whoAmIResult.getAuthorizationID();
1882        }
1883      }
1884      catch (final LDAPException e)
1885      {
1886        Debug.debugException(e);
1887      }
1888    }
1889
1890    if (identity == null)
1891    {
1892      throw new LDAPException(ResultCode.PARAM_ERROR,
1893           ERR_PWMOD_CANNOT_DETERMINE_USER_IDENTITY.get(
1894                userIdentity.getIdentifierString()));
1895    }
1896
1897
1898    final String userDN;
1899    final String lowerIdentity = StaticUtils.toLowerCase(identity);
1900    if (lowerIdentity.startsWith("dn:"))
1901    {
1902      userDN = identity.substring(3).trim();
1903    }
1904    else if (lowerIdentity.startsWith("u:"))
1905    {
1906      final String username = identity.substring(2).trim();
1907      if (username.isEmpty())
1908      {
1909        throw new LDAPException(ResultCode.PARAM_ERROR,
1910             ERR_PWMOD_USER_IDENTITY_EMPTY_USERNAME.get(
1911                  userIdentity.getIdentifierString()));
1912      }
1913
1914      userDN = searchForUser(pool, username, isActiveDirectory);
1915    }
1916    else
1917    {
1918      userDN = identity;
1919    }
1920
1921    final DN parsedUserDN;
1922    try
1923    {
1924      parsedUserDN = new DN(userDN);
1925    }
1926    catch (final LDAPException e)
1927    {
1928      Debug.debugException(e);
1929      throw new LDAPException(ResultCode.PARAM_ERROR,
1930           ERR_PWMOD_USER_IDENTITY_NOT_VALID_DN.get(userDN,
1931                userIdentity.getIdentifierString()),
1932           e);
1933    }
1934
1935    if (parsedUserDN.isNullDN())
1936    {
1937      throw new LDAPException(ResultCode.PARAM_ERROR,
1938           ERR_PWMOD_USER_IDENTITY_EMPTY_DN.get(
1939                userIdentity.getIdentifierString()));
1940    }
1941
1942    if (verbose.isPresent())
1943    {
1944      out();
1945      INFO_PWMOD_USER_IDENTITY_DN_FOR_MOD.get(userDN);
1946    }
1947
1948    return userDN;
1949  }
1950
1951
1952
1953  /**
1954   * Performs a search to determine the DN for the user with the given username.
1955   *
1956   * @param  pool               A connection pool to use to communicate with the
1957   *                            directory server.  It must not be {@code null}.
1958   * @param  username           The username for the target user.  It must not
1959   *                            be {@code null}.
1960   * @param  isActiveDirectory  Indicates whether the target directory server
1961   *                            is believed to be an Active Directory instance.
1962   *
1963   * @return  The DN for the user with the given username.
1964   *
1965   * @throws  LDAPException  If a problem occurs while searching for the user,
1966   *                         or if the search does not match exactly one user.
1967   */
1968  @NotNull()
1969  private String searchForUser(@NotNull final LDAPConnectionPool pool,
1970                               @NotNull final String username,
1971                               final boolean isActiveDirectory)
1972          throws LDAPException
1973  {
1974    // Construct the filter to use for the search.
1975    final List<String> filterAttributeNames;
1976    if (usernameAttribute.isPresent())
1977    {
1978      filterAttributeNames = usernameAttribute.getValues();
1979    }
1980    else if (isActiveDirectory)
1981    {
1982      filterAttributeNames = AD_USERNAME_ATTRIBUTES;
1983    }
1984    else
1985    {
1986      filterAttributeNames = DEFAULT_USERNAME_ATTRIBUTES;
1987    }
1988
1989    final Filter filter;
1990    if (filterAttributeNames.size() == 1)
1991    {
1992      filter = Filter.createEqualityFilter(filterAttributeNames.get(0),
1993           username);
1994    }
1995    else
1996    {
1997      final List<Filter> orComponents =
1998           new ArrayList<>(filterAttributeNames.size());
1999      for (final String attrName : filterAttributeNames)
2000      {
2001        orComponents.add(Filter.createEqualityFilter(attrName, username));
2002      }
2003
2004      filter = Filter.createORFilter(orComponents);
2005    }
2006
2007
2008    // Create the search request to use to find the target user entry.
2009    final SearchRequest searchRequest = new SearchRequest(
2010         searchBaseDN.getStringValue(), SearchScope.SUB, filter,
2011         SearchRequest.NO_ATTRIBUTES);
2012    searchRequest.setSizeLimit(1);
2013
2014    if (verbose.isPresent())
2015    {
2016      out();
2017      wrapOut(0, WRAP_COLUMN,
2018           INFO_PWMOD_ISSUING_SEARCH_FOR_USER.get(
2019                String.valueOf(searchRequest), username));
2020    }
2021
2022
2023    // Issue the search and get the results.
2024    SearchResult searchResult;
2025    LDAPException searchException = null;
2026    try
2027    {
2028      searchResult = pool.search(searchRequest);
2029    }
2030    catch (final LDAPException e)
2031    {
2032      Debug.debugException(e);
2033      searchException = e;
2034      searchResult = new SearchResult(e);
2035    }
2036
2037    if (verbose.isPresent())
2038    {
2039      out();
2040      for (final String line :
2041           ResultUtils.formatResult(searchResult, true, 0, WRAP_COLUMN))
2042      {
2043        out(line);
2044      }
2045    }
2046
2047    if (searchResult.getResultCode() == ResultCode.SUCCESS)
2048    {
2049      if (searchResult.getEntryCount() == 1)
2050      {
2051        return searchResult.getSearchEntries().get(0).getDN();
2052      }
2053      else
2054      {
2055        throw new LDAPException(ResultCode.NO_RESULTS_RETURNED,
2056             ERR_PWMOD_SEARCH_FOR_USER_NO_MATCHES.get(username));
2057      }
2058    }
2059    else if (searchResult.getResultCode() == ResultCode.SIZE_LIMIT_EXCEEDED)
2060    {
2061      throw new LDAPException(ResultCode.SIZE_LIMIT_EXCEEDED,
2062           ERR_PWMOD_SEARCH_FOR_USER_MULTIPLE_MATCHES.get(username),
2063           searchException);
2064    }
2065    else
2066    {
2067      throw new LDAPException(searchResult.getResultCode(),
2068           ERR_PWMOD_SEARCH_FOR_USER_FAILED.get(username,
2069                String.valueOf(searchResult.getResultCode()),
2070                searchResult.getDiagnosticMessage()),
2071           searchException);
2072    }
2073  }
2074
2075
2076
2077  /**
2078   * Retrieves the bytes that comprise the current password for the user, if one
2079   * should be provided in the password update request.
2080   *
2081   * @return  The bytes that comprise the current password for the user, or
2082   *          {@code null} if none should be provided in the password update
2083   *          request.
2084   *
2085   * @throws  LDAPException  If a problem occurs while trying to obtain the
2086   *                         current password.
2087   */
2088  @Nullable()
2089  private byte[] getCurrentPassword()
2090          throws LDAPException
2091  {
2092    if (currentPassword.isPresent())
2093    {
2094      return StaticUtils.getBytes(currentPassword.getValue());
2095    }
2096    else if (currentPasswordFile.isPresent())
2097    {
2098      final File f = currentPasswordFile.getValue();
2099      try
2100      {
2101        final char[] currentPasswordChars =
2102             getPasswordFileReader().readPassword(f);
2103        return StaticUtils.getBytes(new String(currentPasswordChars));
2104      }
2105      catch (final LDAPException e)
2106      {
2107        Debug.debugException(e);
2108        throw new LDAPException(e.getResultCode(),
2109             ERR_PWMOD_CANNOT_READ_CURRENT_PW_FILE.get(f.getAbsolutePath(),
2110                  e.getMessage()),
2111             e);
2112      }
2113      catch (final Exception e)
2114      {
2115        Debug.debugException(e);
2116        throw new LDAPException(ResultCode.LOCAL_ERROR,
2117             ERR_PWMOD_CANNOT_READ_CURRENT_PW_FILE.get(f.getAbsolutePath(),
2118                  StaticUtils.getExceptionMessage(e)),
2119             e);
2120      }
2121    }
2122    else if (promptForCurrentPassword.isPresent())
2123    {
2124      while (true)
2125      {
2126        getOut().print(INFO_PWMOD_PROMPT_CURRENT_PW.get());
2127        try
2128        {
2129          final byte[] pwBytes = PasswordReader.readPassword();
2130          if ((pwBytes == null) || (pwBytes.length == 0))
2131          {
2132            err();
2133            wrapErr(0, WRAP_COLUMN, ERR_PWMOD_PW_EMPTY.get());
2134            err();
2135            continue;
2136          }
2137
2138          return pwBytes;
2139        }
2140        catch (final Exception e)
2141        {
2142          throw new LDAPException(ResultCode.LOCAL_ERROR,
2143               ERR_PWMOD_CANNOT_PROMPT_FOR_CURRENT_PW.get(
2144                    StaticUtils.getExceptionMessage(e)),
2145               e);
2146        }
2147      }
2148    }
2149    else
2150    {
2151      return null;
2152    }
2153  }
2154
2155
2156
2157  /**
2158   * Retrieves the bytes that comprise the new password for the user, if one
2159   * should be provided in the password update request.
2160   *
2161   * @return  The bytes that comprise the new password for the user, or
2162   *          {@code null} if none should be provided in the password update
2163   *          request.
2164   *
2165   * @throws  LDAPException  If a problem occurs while trying to obtain the new
2166   *                         password.
2167   */
2168  @Nullable()
2169  private byte[] getNewPassword()
2170          throws LDAPException
2171  {
2172    if (newPassword.isPresent())
2173    {
2174      return StaticUtils.getBytes(newPassword.getValue());
2175    }
2176    else if (newPasswordFile.isPresent())
2177    {
2178      final File f = newPasswordFile.getValue();
2179      try
2180      {
2181        final char[] newPasswordChars = getPasswordFileReader().readPassword(f);
2182        return StaticUtils.getBytes(new String(newPasswordChars));
2183      }
2184      catch (final LDAPException e)
2185      {
2186        Debug.debugException(e);
2187        throw new LDAPException(e.getResultCode(),
2188             ERR_PWMOD_CANNOT_READ_NEW_PW_FILE.get(f.getAbsolutePath(),
2189                  e.getMessage()),
2190             e);
2191      }
2192      catch (final Exception e)
2193      {
2194        Debug.debugException(e);
2195        throw new LDAPException(ResultCode.LOCAL_ERROR,
2196             ERR_PWMOD_CANNOT_READ_NEW_PW_FILE.get(f.getAbsolutePath(),
2197                  StaticUtils.getExceptionMessage(e)),
2198             e);
2199      }
2200    }
2201    else if (promptForNewPassword.isPresent())
2202    {
2203      while (true)
2204      {
2205        getOut().print(INFO_PWMOD_PROMPT_NEW_PW.get());
2206
2207        final byte[] pwBytes;
2208        try
2209        {
2210          pwBytes = PasswordReader.readPassword();
2211          if ((pwBytes == null) || (pwBytes.length == 0))
2212          {
2213            err();
2214            wrapErr(0, WRAP_COLUMN, ERR_PWMOD_PW_EMPTY.get());
2215            err();
2216            continue;
2217          }
2218
2219          getOut().print(INFO_PWMOD_CONFIRM_NEW_PW.get());
2220          final byte[] confirmBytes = PasswordReader.readPassword();
2221          if ((confirmBytes == null) ||
2222               (! Arrays.equals(pwBytes, confirmBytes)))
2223          {
2224            Arrays.fill(pwBytes, (byte) 0x00);
2225            Arrays.fill(confirmBytes, (byte) 0x00);
2226
2227            err();
2228            wrapErr(0, WRAP_COLUMN, ERR_PWMOD_NEW_PW_MISMATCH.get());
2229            err();
2230            continue;
2231          }
2232
2233          Arrays.fill(confirmBytes, (byte) 0x00);
2234          return pwBytes;
2235        }
2236        catch (final Exception e)
2237        {
2238          Debug.debugException(e);
2239          throw new LDAPException(ResultCode.LOCAL_ERROR,
2240               ERR_PWMOD_CANNOT_PROMPT_FOR_NEW_PW.get(
2241                    StaticUtils.getExceptionMessage(e)),
2242               e);
2243        }
2244      }
2245    }
2246    else if (generateClientSideNewPassword.isPresent())
2247    {
2248      return generatePassword();
2249    }
2250    else
2251    {
2252      return null;
2253    }
2254  }
2255
2256
2257
2258  /**
2259   * Generates a new password for the user.
2260   *
2261   * @return  The new password that was generated.
2262   */
2263  @NotNull()
2264  private byte[] generatePassword()
2265  {
2266    final int length = generatedPasswordLength.getValue();
2267    final StringBuilder generatedPassword = new StringBuilder(length);
2268
2269    final SecureRandom random = ThreadLocalSecureRandom.get();
2270    final StringBuilder allPasswordCharacters = new StringBuilder();
2271    for (final String charSet : generatedPasswordCharacterSet.getValues())
2272    {
2273      allPasswordCharacters.append(charSet);
2274
2275      // Pick one character at random from the provided set to include in the
2276      // password.
2277      generatedPassword.append(
2278           charSet.charAt(random.nextInt(charSet.length())));
2279    }
2280
2281
2282    // Choose as many additional characters (across all of the sets) as needed
2283    // to reach the desired length.
2284    while (generatedPassword.length() < length)
2285    {
2286      generatedPassword.append(allPasswordCharacters.charAt(
2287           random.nextInt(allPasswordCharacters.length())));
2288    }
2289
2290
2291    // Scramble the generated password.
2292    final StringBuilder scrambledPassword =
2293         new StringBuilder(generatedPassword.length());
2294    while (true)
2295    {
2296      if (generatedPassword.length() == 1)
2297      {
2298        scrambledPassword.append(generatedPassword.charAt(0));
2299        break;
2300      }
2301      else
2302      {
2303        final int pos = random.nextInt(generatedPassword.length());
2304        scrambledPassword.append(generatedPassword.charAt(pos));
2305        generatedPassword.deleteCharAt(pos);
2306      }
2307    }
2308
2309    final String scrambledPasswordString = scrambledPassword.toString();
2310    out();
2311    wrapOut(0, WRAP_COLUMN,
2312         INFO_PWMOD_CLIENT_SIDE_GEN_PW.get(getToolName(),
2313              scrambledPasswordString));
2314    return StaticUtils.getBytes(scrambledPasswordString);
2315  }
2316
2317
2318
2319  /**
2320   * Retrieves the controls that should be included in the password update
2321   * request.
2322   *
2323   * @return  The controls that should be included in the password update
2324   *          request, or an empty array if no controls should be included.
2325   *
2326   * @throws  LDAPException  If a problem occurs while trying to create any of
2327   *                         the controls.
2328   */
2329  @NotNull()
2330  private Control[] getUpdateControls()
2331          throws LDAPException
2332  {
2333    final List<Control> controls = new ArrayList<>();
2334
2335    if (updateControl.isPresent())
2336    {
2337      controls.addAll(updateControl.getValues());
2338    }
2339
2340    if (usePasswordPolicyControlOnUpdate.isPresent())
2341    {
2342      controls.add(new PasswordPolicyRequestControl());
2343    }
2344
2345    if (noOperation.isPresent())
2346    {
2347      controls.add(new NoOpRequestControl());
2348    }
2349
2350    if (getPasswordValidationDetails.isPresent())
2351    {
2352      controls.add(new PasswordValidationDetailsRequestControl());
2353    }
2354
2355    if (retireCurrentPassword.isPresent())
2356    {
2357      controls.add(new RetirePasswordRequestControl(false));
2358    }
2359
2360    if (purgeCurrentPassword.isPresent())
2361    {
2362      controls.add(new PurgePasswordRequestControl(false));
2363    }
2364
2365    if (passwordUpdateBehavior.isPresent())
2366    {
2367      controls.add(LDAPModify.createPasswordUpdateBehaviorRequestControl(
2368           passwordUpdateBehavior.getIdentifierString(),
2369           passwordUpdateBehavior.getValues()));
2370    }
2371
2372    if (operationPurpose.isPresent())
2373    {
2374      controls.add(new OperationPurposeRequestControl(false, getToolName(),
2375           getToolVersion(),
2376           LDAPPasswordModify.class.getName() + ".getUpdateControls",
2377           operationPurpose.getValue()));
2378    }
2379
2380    if (useAssuredReplication.isPresent())
2381    {
2382      AssuredReplicationLocalLevel localLevel = null;
2383      if (assuredReplicationLocalLevel.isPresent())
2384      {
2385        final String level = assuredReplicationLocalLevel.getValue();
2386        if (level.equalsIgnoreCase(ASSURED_REPLICATION_LOCAL_LEVEL_NONE))
2387        {
2388          localLevel = AssuredReplicationLocalLevel.NONE;
2389        }
2390        else if (level.equalsIgnoreCase(
2391             ASSURED_REPLICATION_LOCAL_LEVEL_RECEIVED_ANY_SERVER))
2392        {
2393          localLevel = AssuredReplicationLocalLevel.RECEIVED_ANY_SERVER;
2394        }
2395        else if (level.equalsIgnoreCase(
2396             ASSURED_REPLICATION_LOCAL_LEVEL_PROCESSED_ALL_SERVERS))
2397        {
2398          localLevel = AssuredReplicationLocalLevel.PROCESSED_ALL_SERVERS;
2399        }
2400      }
2401
2402      AssuredReplicationRemoteLevel remoteLevel = null;
2403      if (assuredReplicationRemoteLevel.isPresent())
2404      {
2405        final String level = assuredReplicationRemoteLevel.getValue();
2406        if (level.equalsIgnoreCase(ASSURED_REPLICATION_REMOTE_LEVEL_NONE))
2407        {
2408          remoteLevel = AssuredReplicationRemoteLevel.NONE;
2409        }
2410        else if (level.equalsIgnoreCase(
2411             ASSURED_REPLICATION_REMOTE_LEVEL_RECEIVED_ANY_REMOTE_LOCATION))
2412        {
2413          remoteLevel =
2414               AssuredReplicationRemoteLevel.RECEIVED_ANY_REMOTE_LOCATION;
2415        }
2416        else if (level.equalsIgnoreCase(
2417             ASSURED_REPLICATION_REMOTE_LEVEL_RECEIVED_ALL_REMOTE_LOCATIONS))
2418        {
2419          remoteLevel =
2420               AssuredReplicationRemoteLevel.RECEIVED_ALL_REMOTE_LOCATIONS;
2421        }
2422        else if (level.equalsIgnoreCase(
2423             ASSURED_REPLICATION_REMOTE_LEVEL_PROCESSED_ALL_REMOTE_SERVERS))
2424        {
2425          remoteLevel =
2426               AssuredReplicationRemoteLevel.PROCESSED_ALL_REMOTE_SERVERS;
2427        }
2428      }
2429
2430      Long timeoutMillis = null;
2431      if (assuredReplicationTimeout.isPresent())
2432      {
2433        timeoutMillis =
2434             assuredReplicationTimeout.getValue(TimeUnit.MILLISECONDS);
2435      }
2436
2437      controls.add(new AssuredReplicationRequestControl(true, localLevel,
2438           localLevel, remoteLevel, remoteLevel, timeoutMillis, false));
2439    }
2440
2441
2442    return controls.toArray(StaticUtils.NO_CONTROLS);
2443  }
2444
2445
2446
2447  /**
2448   * Writes the provided message and sets it as the completion message.
2449   *
2450   * @param  isError  Indicates whether the message should be written to
2451   *                  standard error rather than standard output.
2452   * @param  message  The message to be written.
2453   */
2454  private void logCompletionMessage(final boolean isError,
2455                                    @NotNull final String message)
2456  {
2457    completionMessage.compareAndSet(null, message);
2458
2459    if (isError)
2460    {
2461      wrapErr(0, WRAP_COLUMN, message);
2462    }
2463    else
2464    {
2465      wrapOut(0, WRAP_COLUMN, message);
2466    }
2467  }
2468
2469
2470
2471  /**
2472   * {@inheritDoc}
2473   */
2474  @Override()
2475  public void handleUnsolicitedNotification(
2476                   @NotNull final LDAPConnection connection,
2477                   @NotNull final ExtendedResult notification)
2478  {
2479    final ArrayList<String> lines = new ArrayList<>(10);
2480    ResultUtils.formatUnsolicitedNotification(lines, notification, true, 0,
2481         WRAP_COLUMN);
2482    for (final String line : lines)
2483    {
2484      err(line);
2485    }
2486    err();
2487  }
2488
2489
2490
2491  /**
2492   * {@inheritDoc}
2493   */
2494  @Override()
2495  @NotNull()
2496  public LinkedHashMap<String[],String> getExampleUsages()
2497  {
2498    final LinkedHashMap<String[],String> examples = new LinkedHashMap<>();
2499
2500    examples.put(
2501         new String[]
2502         {
2503           "--hostname", "ds.example.com",
2504           "--port", "636",
2505           "--useSSL",
2506           "--userIdentity", "u:jdoe",
2507           "--promptForCurrentPassword",
2508           "--promptForNewPassword"
2509         },
2510         INFO_PWMOD_EXAMPLE_1.get());
2511
2512    examples.put(
2513         new String[]
2514         {
2515           "--hostname", "ds.example.com",
2516           "--port", "636",
2517           "--useSSL",
2518           "--bindDN", "uid=admin,dc=example,dc=com",
2519           "--bindPasswordFile", "admin-password.txt",
2520           "--userIdentity", "uid=jdoe,ou=People,dc=example,dc=com",
2521           "--generateClientSideNewPassword",
2522           "--passwordChangeMethod", "ldap-modify"
2523         },
2524         INFO_PWMOD_EXAMPLE_2.get());
2525
2526    return examples;
2527  }
2528}