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 supportsDebugLogging()
549  {
550    return true;
551  }
552
553
554
555  /**
556   * {@inheritDoc}
557   */
558  @Override()
559  protected boolean supportsAuthentication()
560  {
561    return true;
562  }
563
564
565
566  /**
567   * {@inheritDoc}
568   */
569  @Override()
570  protected boolean defaultToPromptForBindPassword()
571  {
572    return true;
573  }
574
575
576
577  /**
578   * {@inheritDoc}
579   */
580  @Override()
581  protected boolean supportsSASLHelp()
582  {
583    return true;
584  }
585
586
587
588  /**
589   * {@inheritDoc}
590   */
591  @Override()
592  protected boolean includeAlternateLongIdentifiers()
593  {
594    return true;
595  }
596
597
598
599  /**
600   * {@inheritDoc}
601   */
602  @Override()
603  @NotNull()
604  protected List<Control> getBindControls()
605  {
606    final List<Control> bindControls = new ArrayList<>(10);
607
608    if (bindControl.isPresent())
609    {
610      bindControls.addAll(bindControl.getValues());
611    }
612
613    if (useAuthorizationIdentityControl.isPresent())
614    {
615      bindControls.add(new AuthorizationIdentityRequestControl(false));
616    }
617
618    if (getAuthorizationEntryAttribute.isPresent())
619    {
620      bindControls.add(new GetAuthorizationEntryRequestControl(true, true,
621           getAuthorizationEntryAttribute.getValues()));
622    }
623
624    if (getUserResourceLimits.isPresent())
625    {
626      bindControls.add(new GetUserResourceLimitsRequestControl());
627    }
628
629    if (usePasswordPolicyControlOnBind.isPresent())
630    {
631      bindControls.add(new PasswordPolicyRequestControl());
632    }
633
634    return bindControls;
635  }
636
637
638
639  /**
640   * {@inheritDoc}
641   */
642  @Override()
643  protected boolean supportsMultipleServers()
644  {
645    return true;
646  }
647
648
649
650  /**
651   * {@inheritDoc}
652   */
653  @Override()
654  protected boolean supportsSSLDebugging()
655  {
656    return true;
657  }
658
659
660
661  /**
662   * {@inheritDoc}
663   */
664  @Override()
665  @NotNull()
666  public LDAPConnectionOptions getConnectionOptions()
667  {
668    final LDAPConnectionOptions options = new LDAPConnectionOptions();
669
670    options.setUseSynchronousMode(true);
671    options.setFollowReferrals(followReferrals.isPresent());
672    options.setUnsolicitedNotificationHandler(this);
673    options.setResponseTimeoutMillis(0L);
674
675    return options;
676  }
677
678
679
680  /**
681   * {@inheritDoc}
682   */
683  @Override()
684  protected boolean logToolInvocationByDefault()
685  {
686    return true;
687  }
688
689
690
691  /**
692   * {@inheritDoc}
693   */
694  @Override()
695  @Nullable()
696  protected String getToolCompletionMessage()
697  {
698    return completionMessage.get();
699  }
700
701
702
703  /**
704   * {@inheritDoc}
705   */
706  @Override()
707  public void addNonLDAPArguments(@NotNull final ArgumentParser parser)
708         throws ArgumentException
709  {
710    argumentParser = parser;
711
712    // Authorization identity arguments.
713    userIdentity = new StringArgument('a', "userIdentity", false, 1,
714         INFO_PWMOD_ARG_PLACEHOLDER_DN_OR_AUTHZID.get(),
715         INFO_PWMOD_ARG_DESC_USER_IDENTITY.get());
716    userIdentity.addLongIdentifier("user-identity", true);
717    userIdentity.addLongIdentifier("userDN", true);
718    userIdentity.addLongIdentifier("user-dn", true);
719    userIdentity.addLongIdentifier("authzID", true);
720    userIdentity.addLongIdentifier("authz-id", true);
721    userIdentity.addLongIdentifier("authorizationID", true);
722    userIdentity.addLongIdentifier("authorization-id", true);
723    userIdentity.setArgumentGroupName(INFO_PWMOD_ARG_GROUP_USER_IDENTITY.get());
724    parser.addArgument(userIdentity);
725
726    provideBindDNAsUserIdentity = new BooleanArgument('A',
727         "provideBindDNAsUserIdentity", 1,
728         INFO_PWMOD_ARG_DESC_PROVIDE_BIND_DN_AS_USER_IDENTITY.get());
729    provideBindDNAsUserIdentity.addLongIdentifier(
730         "provide-bind-dn-as-user-identity", true);
731    provideBindDNAsUserIdentity.addLongIdentifier(
732         "provideBindDNForUserIdentity", true);
733    provideBindDNAsUserIdentity.addLongIdentifier(
734         "provide-bind-dn-for-user-identity", true);
735    provideBindDNAsUserIdentity.addLongIdentifier("provideDNAsUserIdentity",
736         true);
737    provideBindDNAsUserIdentity.addLongIdentifier("provide-dn-as-user-identity",
738         true);
739    provideBindDNAsUserIdentity.addLongIdentifier("provideDNForUserIdentity",
740         true);
741    provideBindDNAsUserIdentity.addLongIdentifier(
742         "provide-dn-for-user-identity", true);
743    provideBindDNAsUserIdentity.addLongIdentifier("useBindDNAsUserIdentity",
744         true);
745    provideBindDNAsUserIdentity.addLongIdentifier(
746         "use-bind-dn-as-user-identity", true);
747    provideBindDNAsUserIdentity.addLongIdentifier("useBindDNForUserIdentity",
748         true);
749    provideBindDNAsUserIdentity.addLongIdentifier(
750         "use-bind-dn-for-user-identity", true);
751    provideBindDNAsUserIdentity.addLongIdentifier("useDNAsUserIdentity", true);
752    provideBindDNAsUserIdentity.addLongIdentifier("use-dn-as-user-identity",
753         true);
754    provideBindDNAsUserIdentity.addLongIdentifier("useDNForUserIdentity", true);
755    provideBindDNAsUserIdentity.addLongIdentifier("use-dn-for-user-identity",
756         true);
757    provideBindDNAsUserIdentity.addLongIdentifier("useBindDNForAuthzID", true);
758    provideBindDNAsUserIdentity.addLongIdentifier("use-bind-dn-for-authz-id",
759         true);
760    provideBindDNAsUserIdentity.addLongIdentifier("provideDNForAuthzID", true);
761    provideBindDNAsUserIdentity.addLongIdentifier("provide-dn-for-authz-id",
762         true);
763    provideBindDNAsUserIdentity.setArgumentGroupName(
764         INFO_PWMOD_ARG_GROUP_USER_IDENTITY.get());
765    parser.addArgument(provideBindDNAsUserIdentity);
766
767    usernameAttribute = new StringArgument(null, "usernameAttribute", false, 0,
768         INFO_PWMOD_ARG_PLACEHOLDER_ATTRIBUTE_NAME.get(),
769         INFO_PWMOD_ARG_DESC_USERNAME_ATTRIBUTE.get());
770    usernameAttribute.addLongIdentifier("username-attribute", true);
771    usernameAttribute.addLongIdentifier("usernameAttr", true);
772    usernameAttribute.addLongIdentifier("username-attr", true);
773    usernameAttribute.addLongIdentifier("userIDAttribute", true);
774    usernameAttribute.addLongIdentifier("user-id-attribute", true);
775    usernameAttribute.addLongIdentifier("userIDAttr", true);
776    usernameAttribute.addLongIdentifier("user-id-attr", true);
777    usernameAttribute.setArgumentGroupName(
778         INFO_PWMOD_ARG_GROUP_USER_IDENTITY.get());
779    parser.addArgument(usernameAttribute);
780
781    searchBaseDN = new DNArgument('b', "searchBaseDN", false, 0, null,
782         INFO_PWMOD_ARG_DESC_SEARCH_BASE_DN.get(), DN.NULL_DN);
783    searchBaseDN.addLongIdentifier("search-base-dn", true);
784    searchBaseDN.addLongIdentifier("baseDN", true);
785    searchBaseDN.addLongIdentifier("base-dn", true);
786    searchBaseDN.setArgumentGroupName(INFO_PWMOD_ARG_GROUP_USER_IDENTITY.get());
787    parser.addArgument(searchBaseDN);
788
789
790    // New password arguments.
791    newPassword = new StringArgument('n', "newPassword", false, 1,
792         INFO_PWMOD_ARG_PLACEHOLDER_PASSWORD.get(),
793         INFO_PWMOD_ARG_DESC_NEW_PASSWORD.get());
794    newPassword.addLongIdentifier("new-password", true);
795    newPassword.addLongIdentifier("newPW", true);
796    newPassword.addLongIdentifier("new-pw", true);
797    newPassword.addLongIdentifier("new", true);
798    newPassword.setArgumentGroupName(INFO_PWMOD_ARG_GROUP_NEW_PASSWORD.get());
799    parser.addArgument(newPassword);
800
801    newPasswordFile = new FileArgument('N', "newPasswordFile", false, 1, null,
802         INFO_PWMOD_ARG_DESC_NEW_PASSWORD_FILE.get(), true, true, true, false);
803    newPasswordFile.addLongIdentifier("new-password-file", true);
804    newPasswordFile.addLongIdentifier("newPWFile", true);
805    newPasswordFile.addLongIdentifier("new-pw-file", true);
806    newPasswordFile.addLongIdentifier("newFile", true);
807    newPasswordFile.addLongIdentifier("new-file", true);
808    newPasswordFile.addLongIdentifier("newPasswordPath", true);
809    newPasswordFile.addLongIdentifier("new-password-path", true);
810    newPasswordFile.addLongIdentifier("newPWPath", true);
811    newPasswordFile.addLongIdentifier("new-pw-path", true);
812    newPasswordFile.addLongIdentifier("newPath", true);
813    newPasswordFile.addLongIdentifier("new-path", true);
814    newPasswordFile.setArgumentGroupName(
815         INFO_PWMOD_ARG_GROUP_NEW_PASSWORD.get());
816    parser.addArgument(newPasswordFile);
817
818    promptForNewPassword = new BooleanArgument(null, "promptForNewPassword", 1,
819         INFO_PWMOD_ARG_DESC_PROMPT_FOR_NEW_PASSWORD.get());
820    promptForNewPassword.addLongIdentifier("prompt-for-new-password", true);
821    promptForNewPassword.addLongIdentifier("promptForNewPW", true);
822    promptForNewPassword.addLongIdentifier("prompt-for-new-pw", true);
823    promptForNewPassword.addLongIdentifier("promptForNew", true);
824    promptForNewPassword.addLongIdentifier("prompt-for-new", true);
825    promptForNewPassword.addLongIdentifier("promptNew", true);
826    promptForNewPassword.addLongIdentifier("prompt-new", true);
827    promptForNewPassword.setArgumentGroupName(
828         INFO_PWMOD_ARG_GROUP_NEW_PASSWORD.get());
829    parser.addArgument(promptForNewPassword);
830
831    generateClientSideNewPassword = new BooleanArgument(null,
832         "generateClientSideNewPassword", 1,
833         INFO_PWMOD_ARG_DESC_GENERATE_CLIENT_SIDE_NEW_PASSWORD.get());
834    generateClientSideNewPassword.addLongIdentifier(
835         "generate-client-side-new-password", true);
836    generateClientSideNewPassword.addLongIdentifier("generateClientSideNewPW",
837         true);
838    generateClientSideNewPassword.addLongIdentifier(
839         "generate-client-side-new-pw", true);
840    generateClientSideNewPassword.addLongIdentifier("generateNewPassword",
841         true);
842    generateClientSideNewPassword.addLongIdentifier("generate-new-password",
843         true);
844    generateClientSideNewPassword.addLongIdentifier("generateNewPW", true);
845    generateClientSideNewPassword.addLongIdentifier("generate-new-pw", true);
846    generateClientSideNewPassword.addLongIdentifier("generatePassword", true);
847    generateClientSideNewPassword.addLongIdentifier("generate-password", true);
848    generateClientSideNewPassword.addLongIdentifier("generatePW", true);
849    generateClientSideNewPassword.addLongIdentifier("generate-pw", true);
850    generateClientSideNewPassword.setArgumentGroupName(
851         INFO_PWMOD_ARG_GROUP_NEW_PASSWORD.get());
852    parser.addArgument(generateClientSideNewPassword);
853
854    generatedPasswordLength = new IntegerArgument(null,
855         "generatedPasswordLength", false, 1,
856         INFO_PWMOD_ARG_PLACEHOLDER_LENGTH.get(),
857         INFO_PWMOD_ARG_DESC_GENERATED_PASSWORD_LENGTH.get(), 1,
858         Integer.MAX_VALUE, 12);
859    generatedPasswordLength.addLongIdentifier("generated-password-length",
860         true);
861    generatedPasswordLength.addLongIdentifier("generatedPWLength", true);
862    generatedPasswordLength.addLongIdentifier("generated-pw-length", true);
863    generatedPasswordLength.addLongIdentifier("passwordLength", true);
864    generatedPasswordLength.addLongIdentifier("password-length", true);
865    generatedPasswordLength.addLongIdentifier("pwLength", true);
866    generatedPasswordLength.addLongIdentifier("pw-length", true);
867    generatedPasswordLength.setArgumentGroupName(
868         INFO_PWMOD_ARG_GROUP_NEW_PASSWORD.get());
869    parser.addArgument(generatedPasswordLength);
870
871    generatedPasswordCharacterSet = new StringArgument(null,
872         "generatedPasswordCharacterSet", false, 0,
873         INFO_PWMOD_ARG_PLACEHOLDER_CHARS.get(),
874         INFO_PWMOD_ARG_DESC_GENERATED_PASSWORD_CHARACTER_SET.get(), null,
875         Collections.unmodifiableList(Arrays.asList(
876              "abcdefghijmnopqrstuvwxyz", // Note that some letters and
877              "ABCDEFGHJLMNPQRSTUVWXYZ",  // digits are missing in an attempt
878              "23456789",                 // to avoid ambiguous characters.
879              "@#-_=+.")));
880    generatedPasswordCharacterSet.addLongIdentifier(
881         "generated-password-character-set", true);
882    generatedPasswordCharacterSet.addLongIdentifier("generatedPWCharacterSet",
883         true);
884    generatedPasswordCharacterSet.addLongIdentifier(
885         "generated-pw-character-set", true);
886    generatedPasswordCharacterSet.addLongIdentifier("generatedPasswordCharSet",
887         true);
888    generatedPasswordCharacterSet.addLongIdentifier(
889         "generated-password-char-set", true);
890    generatedPasswordCharacterSet.addLongIdentifier(
891         "generated-password-charset", true);
892    generatedPasswordCharacterSet.addLongIdentifier("generatedPWCharSet", true);
893    generatedPasswordCharacterSet.addLongIdentifier("generated-pw-char-set",
894         true);
895    generatedPasswordCharacterSet.addLongIdentifier("generated-pw-charset",
896         true);
897    generatedPasswordCharacterSet.addLongIdentifier(
898         "generatedPasswordCharacters", true);
899    generatedPasswordCharacterSet.addLongIdentifier(
900         "generated-password-characters", true);
901    generatedPasswordCharacterSet.addLongIdentifier("generatedPWCharacters",
902         true);
903    generatedPasswordCharacterSet.addLongIdentifier("generated-pw-characters",
904         true);
905    generatedPasswordCharacterSet.addLongIdentifier("generatedPasswordChars",
906         true);
907    generatedPasswordCharacterSet.addLongIdentifier("generated-password-chars",
908         true);
909    generatedPasswordCharacterSet.addLongIdentifier("generatedPWChars", true);
910    generatedPasswordCharacterSet.addLongIdentifier("generated-pw-chars", true);
911    generatedPasswordCharacterSet.addLongIdentifier("passwordCharacters", true);
912    generatedPasswordCharacterSet.addLongIdentifier("password-characters",
913         true);
914    generatedPasswordCharacterSet.addLongIdentifier("pwCharacters", true);
915    generatedPasswordCharacterSet.addLongIdentifier("pw-characters", true);
916    generatedPasswordCharacterSet.addLongIdentifier("passwordCharacterSet",
917         true);
918    generatedPasswordCharacterSet.addLongIdentifier("password-character-set",
919         true);
920    generatedPasswordCharacterSet.addLongIdentifier("pwCharacterSet", true);
921    generatedPasswordCharacterSet.addLongIdentifier("pw-character-set", true);
922    generatedPasswordCharacterSet.addLongIdentifier("passwordCharSet", true);
923    generatedPasswordCharacterSet.addLongIdentifier("password-charset", true);
924    generatedPasswordCharacterSet.addLongIdentifier("password-char-set", true);
925    generatedPasswordCharacterSet.addLongIdentifier("pwCharSet", true);
926    generatedPasswordCharacterSet.addLongIdentifier("pw-charset", true);
927    generatedPasswordCharacterSet.addLongIdentifier("pw-char-set", true);
928    generatedPasswordCharacterSet.addLongIdentifier("passwordChars", true);
929    generatedPasswordCharacterSet.addLongIdentifier("password-chars", true);
930    generatedPasswordCharacterSet.addLongIdentifier("pw-chars", true);
931    generatedPasswordCharacterSet.setArgumentGroupName(
932         INFO_PWMOD_ARG_GROUP_NEW_PASSWORD.get());
933    parser.addArgument(generatedPasswordCharacterSet);
934
935
936    // Current password arguments.
937    currentPassword = new StringArgument('c', "currentPassword", false, 1,
938         INFO_PWMOD_ARG_PLACEHOLDER_PASSWORD.get(),
939         INFO_PWMOD_ARG_DESC_CURRENT_PASSWORD.get());
940    currentPassword.addLongIdentifier("current-password", true);
941    currentPassword.addLongIdentifier("currentPW", true);
942    currentPassword.addLongIdentifier("current-pw", true);
943    currentPassword.addLongIdentifier("current", true);
944    currentPassword.addLongIdentifier("oldPassword", true);
945    currentPassword.addLongIdentifier("old-password", true);
946    currentPassword.addLongIdentifier("oldPW", true);
947    currentPassword.addLongIdentifier("old-pw", true);
948    currentPassword.addLongIdentifier("old", true);
949    currentPassword.setArgumentGroupName(
950         INFO_PWMOD_ARG_GROUP_CURRENT_PASSWORD.get());
951    parser.addArgument(currentPassword);
952
953    currentPasswordFile = new FileArgument('C', "currentPasswordFile", false, 1,
954         null, INFO_PWMOD_ARG_DESC_CURRENT_PASSWORD_FILE.get(), true, true,
955         true, false);
956    currentPasswordFile.addLongIdentifier("current-password-file", true);
957    currentPasswordFile.addLongIdentifier("currentPWFile", true);
958    currentPasswordFile.addLongIdentifier("current-pw-file", true);
959    currentPasswordFile.addLongIdentifier("currentFile", true);
960    currentPasswordFile.addLongIdentifier("current-file", true);
961    currentPasswordFile.addLongIdentifier("currentPasswordPath", true);
962    currentPasswordFile.addLongIdentifier("current-password-path", true);
963    currentPasswordFile.addLongIdentifier("currentPWPath", true);
964    currentPasswordFile.addLongIdentifier("current-pw-path", true);
965    currentPasswordFile.addLongIdentifier("currentPath", true);
966    currentPasswordFile.addLongIdentifier("current-path", true);
967    currentPasswordFile.addLongIdentifier("oldPasswordFile", true);
968    currentPasswordFile.addLongIdentifier("old-password-file", true);
969    currentPasswordFile.addLongIdentifier("oldPWFile", true);
970    currentPasswordFile.addLongIdentifier("old-pw-file", true);
971    currentPasswordFile.addLongIdentifier("oldFile", true);
972    currentPasswordFile.addLongIdentifier("old-file", true);
973    currentPasswordFile.addLongIdentifier("oldPasswordPath", true);
974    currentPasswordFile.addLongIdentifier("old-password-path", true);
975    currentPasswordFile.addLongIdentifier("oldPWPath", true);
976    currentPasswordFile.addLongIdentifier("old-pw-path", true);
977    currentPasswordFile.addLongIdentifier("oldPath", true);
978    currentPasswordFile.addLongIdentifier("old-path", true);
979    currentPasswordFile.setArgumentGroupName(
980         INFO_PWMOD_ARG_GROUP_CURRENT_PASSWORD.get());
981    parser.addArgument(currentPasswordFile);
982
983    promptForCurrentPassword = new BooleanArgument(null,
984         "promptForCurrentPassword", 1,
985         INFO_PWMOD_ARG_DESC_PROMPT_FOR_CURRENT_PASSWORD.get());
986    promptForCurrentPassword.addLongIdentifier("prompt-for-current-password",
987         true);
988    promptForCurrentPassword.addLongIdentifier("promptForCurrentPW", true);
989    promptForCurrentPassword.addLongIdentifier("prompt-for-current-pw", true);
990    promptForCurrentPassword.addLongIdentifier("promptForCurrent", true);
991    promptForCurrentPassword.addLongIdentifier("prompt-for-current", true);
992    promptForCurrentPassword.addLongIdentifier("promptCurrent", true);
993    promptForCurrentPassword.addLongIdentifier("prompt-current", true);
994    promptForCurrentPassword.addLongIdentifier("promptForOldPassword", true);
995    promptForCurrentPassword.addLongIdentifier("prompt-for-old-password", true);
996    promptForCurrentPassword.addLongIdentifier("promptForOldPW", true);
997    promptForCurrentPassword.addLongIdentifier("prompt-for-old-pw", true);
998    promptForCurrentPassword.addLongIdentifier("promptForOld", true);
999    promptForCurrentPassword.addLongIdentifier("prompt-for-old", true);
1000    promptForCurrentPassword.addLongIdentifier("promptOld", true);
1001    promptForCurrentPassword.addLongIdentifier("prompt-old", true);
1002    promptForCurrentPassword.setArgumentGroupName(
1003         INFO_PWMOD_ARG_GROUP_CURRENT_PASSWORD.get());
1004    parser.addArgument(promptForCurrentPassword);
1005
1006
1007    // Bind control arguments.
1008    bindControl = new ControlArgument(null, "bindControl", false, 0, null,
1009         INFO_PWMOD_ARG_DESC_BIND_CONTROL.get());
1010    bindControl.addLongIdentifier("bind-control", true);
1011    bindControl.setArgumentGroupName(INFO_PWMOD_ARG_GROUP_BIND_CONTROL.get());
1012    parser.addArgument(bindControl);
1013
1014    useAuthorizationIdentityControl = new BooleanArgument(null,
1015         "useAuthorizationIdentityControl", 1,
1016         INFO_PWMOD_ARG_DESC_USE_AUTHZ_ID_CONTROL.get());
1017    useAuthorizationIdentityControl.addLongIdentifier(
1018         "use-authorization-identity-control", true);
1019    useAuthorizationIdentityControl.addLongIdentifier(
1020         "useAuthorizationID-control", true);
1021    useAuthorizationIdentityControl.addLongIdentifier(
1022         "use-authorization-id-control", true);
1023    useAuthorizationIdentityControl.addLongIdentifier(
1024         "authorizationIdentityControl", true);
1025    useAuthorizationIdentityControl.addLongIdentifier(
1026         "authorization-identity-control", true);
1027    useAuthorizationIdentityControl.addLongIdentifier("authorizationIDControl",
1028         true);
1029    useAuthorizationIdentityControl.addLongIdentifier(
1030         "authorization-id-control", true);
1031    useAuthorizationIdentityControl.addLongIdentifier("authzIDControl", true);
1032    useAuthorizationIdentityControl.addLongIdentifier("authz-id-control", true);
1033    useAuthorizationIdentityControl.setArgumentGroupName(
1034         INFO_PWMOD_ARG_GROUP_BIND_CONTROL.get());
1035    parser.addArgument(useAuthorizationIdentityControl);
1036
1037    usePasswordPolicyControlOnBind = new BooleanArgument(null,
1038         "usePasswordPolicyControlOnBind", 1,
1039         INFO_PWMOD_ARG_DESC_USE_PW_POLICY_CONTROL_ON_BIND.get());
1040    usePasswordPolicyControlOnBind.addLongIdentifier(
1041         "use-password-policy-control-on-bind", true);
1042    usePasswordPolicyControlOnBind.addLongIdentifier("usePWPolicyControlOnBind",
1043         true);
1044    usePasswordPolicyControlOnBind.addLongIdentifier(
1045         "use-pw-policy-control-on-bind", true);
1046    usePasswordPolicyControlOnBind.setArgumentGroupName(
1047         INFO_PWMOD_ARG_GROUP_BIND_CONTROL.get());
1048    parser.addArgument(usePasswordPolicyControlOnBind);
1049
1050    getAuthorizationEntryAttribute = new StringArgument(null,
1051         "getAuthorizationEntryAttribute", false, 0,
1052         INFO_PWMOD_ARG_PLACEHOLDER_ATTRIBUTE_NAME.get(),
1053         INFO_PWMOD_ARG_DESC_GET_AUTHZ_ENTRY_ATTRIBUTE.get());
1054    getAuthorizationEntryAttribute.addLongIdentifier(
1055         "get-authorization-entry-attribute", true);
1056    getAuthorizationEntryAttribute.setArgumentGroupName(
1057         INFO_PWMOD_ARG_GROUP_BIND_CONTROL.get());
1058    parser.addArgument(getAuthorizationEntryAttribute);
1059
1060    getUserResourceLimits = new BooleanArgument(null, "getUserResourceLimits",
1061         1, INFO_PWMOD_ARG_DESC_GET_USER_RESOURCE_LIMITS.get());
1062    getUserResourceLimits.addLongIdentifier("get-user-resource-limits", true);
1063    getUserResourceLimits.setArgumentGroupName(
1064         INFO_PWMOD_ARG_GROUP_BIND_CONTROL.get());
1065    parser.addArgument(getUserResourceLimits);
1066
1067
1068    // Update control arguments.
1069    updateControl = new ControlArgument('J', "updateControl", false, 0, null,
1070         INFO_PWMOD_ARG_DESC_UPDATE_CONTROL.get());
1071    updateControl.addLongIdentifier("update-control", true);
1072    updateControl.addLongIdentifier("control", true);
1073    updateControl.setArgumentGroupName(
1074         INFO_PWMOD_ARG_GROUP_UPDATE_CONTROL.get());
1075    parser.addArgument(updateControl);
1076
1077    usePasswordPolicyControlOnUpdate = new BooleanArgument(null,
1078         "usePasswordPolicyControlOnUpdate", 1,
1079         INFO_PWMOD_ARG_DESC_USE_PW_POLICY_CONTROL_ON_UPDATE.get());
1080    usePasswordPolicyControlOnUpdate.addLongIdentifier(
1081         "use-password-policy-control-on-update", true);
1082    usePasswordPolicyControlOnUpdate.addLongIdentifier(
1083         "usePWPolicyControlOnUpdate", true);
1084    usePasswordPolicyControlOnUpdate.addLongIdentifier(
1085         "use-pw-policy-control-on-update", true);
1086    usePasswordPolicyControlOnUpdate.setArgumentGroupName(
1087         INFO_PWMOD_ARG_GROUP_UPDATE_CONTROL.get());
1088    parser.addArgument(usePasswordPolicyControlOnUpdate);
1089
1090    noOperation = new BooleanArgument(null, "noOperation", 1,
1091         INFO_PWMOD_ARG_DESC_NO_OPERATION.get());
1092    noOperation.addLongIdentifier("no-operation", true);
1093    noOperation.addLongIdentifier("noOp", true);
1094    noOperation.addLongIdentifier("no-op", true);
1095    noOperation.setArgumentGroupName(INFO_PWMOD_ARG_GROUP_UPDATE_CONTROL.get());
1096    parser.addArgument(noOperation);
1097
1098    getPasswordValidationDetails = new BooleanArgument(null,
1099         "getPasswordValidationDetails", 1,
1100         INFO_PWMOD_ARG_DESC_GET_PW_VALIDATION_DETAILS.get());
1101    getPasswordValidationDetails.addLongIdentifier(
1102         "get-password-validation-details", true);
1103    getPasswordValidationDetails.addLongIdentifier("getPWValidationDetails",
1104         true);
1105    getPasswordValidationDetails.addLongIdentifier("get-pw-validation-details",
1106         true);
1107    getPasswordValidationDetails.setArgumentGroupName(
1108         INFO_PWMOD_ARG_GROUP_UPDATE_CONTROL.get());
1109    parser.addArgument(getPasswordValidationDetails);
1110
1111    retireCurrentPassword = new BooleanArgument(null, "retireCurrentPassword",
1112         1, INFO_PWMOD_ARG_DESC_RETIRE_CURRENT_PASSWORD.get());
1113    retireCurrentPassword.addLongIdentifier("retire-current-password", true);
1114    retireCurrentPassword.addLongIdentifier("retireCurrentPW", true);
1115    retireCurrentPassword.addLongIdentifier("retire-current-pw", true);
1116    retireCurrentPassword.addLongIdentifier("retirePassword", true);
1117    retireCurrentPassword.addLongIdentifier("retire-password", true);
1118    retireCurrentPassword.addLongIdentifier("retirePW", true);
1119    retireCurrentPassword.addLongIdentifier("retire-pw", true);
1120    retireCurrentPassword.setArgumentGroupName(
1121         INFO_PWMOD_ARG_GROUP_UPDATE_CONTROL.get());
1122    parser.addArgument(retireCurrentPassword);
1123
1124    purgeCurrentPassword = new BooleanArgument(null, "purgeCurrentPassword", 1,
1125         INFO_PWMOD_ARG_DESC_PURGE_CURRENT_PASSWORD.get());
1126    purgeCurrentPassword.addLongIdentifier("purge-current-password", true);
1127    purgeCurrentPassword.addLongIdentifier("purgeCurrentPW", true);
1128    purgeCurrentPassword.addLongIdentifier("purge-current-pw", true);
1129    purgeCurrentPassword.addLongIdentifier("purgePassword", true);
1130    purgeCurrentPassword.addLongIdentifier("purge-password", true);
1131    purgeCurrentPassword.addLongIdentifier("purgePW", true);
1132    purgeCurrentPassword.addLongIdentifier("purge-pw", true);
1133    purgeCurrentPassword.setArgumentGroupName(
1134         INFO_PWMOD_ARG_GROUP_UPDATE_CONTROL.get());
1135    parser.addArgument(purgeCurrentPassword);
1136
1137    passwordUpdateBehavior = new StringArgument(null,
1138         "passwordUpdateBehavior", false, 0,
1139         INFO_PWMOD_ARG_PLACEHOLDER_NAME_VALUE.get(),
1140         INFO_PWMOD_ARG_DESC_PASSWORD_UPDATE_BEHAVIOR.get());
1141    passwordUpdateBehavior.addLongIdentifier("password-update-behavior", true);
1142    passwordUpdateBehavior.addLongIdentifier("pwUpdateBehavior", true);
1143    passwordUpdateBehavior.addLongIdentifier("pw-update-behavior", true);
1144    passwordUpdateBehavior.addLongIdentifier("updateBehavior", true);
1145    passwordUpdateBehavior.addLongIdentifier("update-behavior", true);
1146    passwordUpdateBehavior.setArgumentGroupName(
1147         INFO_PWMOD_ARG_GROUP_UPDATE_CONTROL.get());
1148    parser.addArgument(passwordUpdateBehavior);
1149
1150    useAssuredReplication = new BooleanArgument(null, "useAssuredReplication",
1151         1, INFO_PWMOD_ARG_DESC_ASSURED_REPLICATION.get());
1152    useAssuredReplication.addLongIdentifier("use-assured-replication", true);
1153    useAssuredReplication.addLongIdentifier("assuredReplication", true);
1154    useAssuredReplication.addLongIdentifier("assured-replication", true);
1155    useAssuredReplication.setArgumentGroupName(
1156         INFO_PWMOD_ARG_GROUP_UPDATE_CONTROL.get());
1157    parser.addArgument(useAssuredReplication);
1158
1159    assuredReplicationLocalLevel = new StringArgument(null,
1160         "assuredReplicationLocalLevel", false, 1,
1161         INFO_PWMOD_ARG_PLACEHOLDER_LEVEL.get(),
1162         INFO_PWMOD_ARG_DESC_ASSURED_REPLICATION_LOCAL_LEVEL.get(),
1163         StaticUtils.setOf(
1164              ASSURED_REPLICATION_LOCAL_LEVEL_NONE,
1165              ASSURED_REPLICATION_LOCAL_LEVEL_RECEIVED_ANY_SERVER,
1166              ASSURED_REPLICATION_LOCAL_LEVEL_PROCESSED_ALL_SERVERS));
1167    assuredReplicationLocalLevel.addLongIdentifier(
1168         "assured-replication-local-level", true);
1169    assuredReplicationLocalLevel.addLongIdentifier("localLevel", true);
1170    assuredReplicationLocalLevel.addLongIdentifier("local-level", true);
1171    assuredReplicationLocalLevel.setArgumentGroupName(
1172         INFO_PWMOD_ARG_GROUP_UPDATE_CONTROL.get());
1173    parser.addArgument(assuredReplicationLocalLevel);
1174
1175    assuredReplicationRemoteLevel = new StringArgument(null,
1176         "assuredReplicationRemoteLevel", false, 1,
1177         INFO_PWMOD_ARG_PLACEHOLDER_LEVEL.get(),
1178         INFO_PWMOD_ARG_DESC_ASSURED_REPLICATION_REMOTE_LEVEL.get(),
1179         StaticUtils.setOf(
1180              ASSURED_REPLICATION_REMOTE_LEVEL_NONE,
1181              ASSURED_REPLICATION_REMOTE_LEVEL_RECEIVED_ANY_REMOTE_LOCATION,
1182              ASSURED_REPLICATION_REMOTE_LEVEL_RECEIVED_ALL_REMOTE_LOCATIONS,
1183              ASSURED_REPLICATION_REMOTE_LEVEL_PROCESSED_ALL_REMOTE_SERVERS));
1184    assuredReplicationRemoteLevel.addLongIdentifier(
1185         "assured-replication-remote-level", true);
1186    assuredReplicationRemoteLevel.addLongIdentifier("remoteLevel", true);
1187    assuredReplicationRemoteLevel.addLongIdentifier("remote-level", true);
1188    assuredReplicationRemoteLevel.setArgumentGroupName(
1189         INFO_PWMOD_ARG_GROUP_UPDATE_CONTROL.get());
1190    parser.addArgument(assuredReplicationRemoteLevel);
1191
1192    assuredReplicationTimeout = new DurationArgument(null,
1193         "assuredReplicationTimeout", false,
1194         INFO_PWMOD_ARG_PLACEHOLDER_TIMEOUT.get(),
1195         INFO_PWMOD_ARG_DESC_ASSURED_REPLICATION_TIMEOUT.get());
1196    assuredReplicationTimeout.addLongIdentifier("assured-replication-timeout",
1197         true);
1198    assuredReplicationTimeout.setArgumentGroupName(
1199         INFO_PWMOD_ARG_GROUP_UPDATE_CONTROL.get());
1200    parser.addArgument(assuredReplicationTimeout);
1201
1202    operationPurpose = new StringArgument(null, "operationPurpose", false, 1,
1203         INFO_PWMOD_ARG_PLACEHOLDER_PURPOSE.get(),
1204         INFO_PWMOD_ARG_DESC_OPERATION_PURPOSE.get());
1205    operationPurpose.addLongIdentifier("operation-purpose", true);
1206    operationPurpose.setArgumentGroupName(
1207         INFO_PWMOD_ARG_GROUP_UPDATE_CONTROL.get());
1208    parser.addArgument(operationPurpose);
1209
1210
1211    // Other arguments
1212    passwordAttribute = new StringArgument(null, "passwordAttribute", false, 1,
1213         INFO_PWMOD_ARG_PLACEHOLDER_ATTRIBUTE_NAME.get(),
1214         INFO_PWMOD_ARG_DESC_PASSWORD_ATTRIBUTE.get(),
1215         DEFAULT_PASSWORD_ATTRIBUTE);
1216    passwordAttribute.addLongIdentifier("password-attribute", true);
1217    passwordAttribute.addLongIdentifier("passwordAttr", true);
1218    passwordAttribute.addLongIdentifier("password-attr", true);
1219    passwordAttribute.addLongIdentifier("pwAttribute", true);
1220    passwordAttribute.addLongIdentifier("pw-attribute", true);
1221    passwordAttribute.addLongIdentifier("pwAttr", true);
1222    passwordAttribute.addLongIdentifier("pw-attr", true);
1223    passwordAttribute.setArgumentGroupName(
1224         INFO_PWMOD_ARG_GROUP_OTHER.get());
1225
1226    passwordChangeMethod = new StringArgument(null, "passwordChangeMethod",
1227         false, 1, INFO_PWMOD_ARG_PLACEHOLDER_CHANGE_METHOD.get(),
1228         INFO_PWMOD_ARG_DESC_PASSWORD_CHANGE_METHOD.get(),
1229         StaticUtils.setOf(
1230              PASSWORD_CHANGE_METHOD_PW_MOD_EXTOP,
1231              PASSWORD_CHANGE_METHOD_LDAP_MOD,
1232              PASSWORD_CHANGE_METHOD_AD));
1233    passwordChangeMethod.addLongIdentifier("password-change-method", true);
1234    passwordChangeMethod.addLongIdentifier("pwChangeMethod", true);
1235    passwordChangeMethod.addLongIdentifier("pw-change-method", true);
1236    passwordChangeMethod.addLongIdentifier("changeMethod", true);
1237    passwordChangeMethod.addLongIdentifier("change-method", true);
1238    passwordChangeMethod.addLongIdentifier("method", true);
1239    passwordChangeMethod.setArgumentGroupName(
1240         INFO_PWMOD_ARG_GROUP_OTHER.get());
1241    parser.addArgument(passwordChangeMethod);
1242
1243    followReferrals = new BooleanArgument(null, "followReferrals", 1,
1244         INFO_PWMOD_ARG_DESC_FOLLOW_REFERRALS.get());
1245    followReferrals.addLongIdentifier("follow-referrals", true);
1246    followReferrals.setArgumentGroupName(
1247         INFO_PWMOD_ARG_GROUP_OTHER.get());
1248    parser.addArgument(followReferrals);
1249
1250    useAdministrativeSession = new BooleanArgument(null,
1251         "useAdministrativeSession", 1,
1252         INFO_PWMOD_ARG_DESC_USE_ADMIN_SESSION.get());
1253    useAdministrativeSession.addLongIdentifier("use-administrative-session",
1254         true);
1255    useAdministrativeSession.addLongIdentifier("useAdminSession", true);
1256    useAdministrativeSession.addLongIdentifier("use-admin-session", true);
1257    useAdministrativeSession.addLongIdentifier("administrativeSession", true);
1258    useAdministrativeSession.addLongIdentifier("administrative-session", true);
1259    useAdministrativeSession.addLongIdentifier("adminSession", true);
1260    useAdministrativeSession.addLongIdentifier("admin-session", true);
1261    useAdministrativeSession.setArgumentGroupName(
1262         INFO_PWMOD_ARG_GROUP_OTHER.get());
1263    parser.addArgument(useAdministrativeSession);
1264
1265    verbose = new BooleanArgument('v', "verbose", 1,
1266         INFO_PWMOD_ARG_DESC_VERBOSE.get());
1267    verbose.setArgumentGroupName(INFO_PWMOD_ARG_GROUP_OTHER.get());
1268    parser.addArgument(verbose);
1269
1270    // This argument isn't actually used, but provides command-line backward
1271    // compatibility with an existing implementation.
1272    scriptFriendly = new BooleanArgument(null, "script-friendly", 1,
1273         INFO_PWMOD_ARG_DESC_SCRIPT_FRIENDLY.get());
1274    scriptFriendly.setArgumentGroupName(INFO_PWMOD_ARG_GROUP_OTHER.get());
1275    scriptFriendly.setHidden(true);
1276    parser.addArgument(scriptFriendly);
1277
1278
1279    // Argument constraints.
1280    parser.addExclusiveArgumentSet(userIdentity, provideBindDNAsUserIdentity);
1281
1282    final DNArgument bindDNArgument =
1283         parser.getDNArgument(BIND_DN_ARGUMENT_LONG_IDENTIFIER);
1284    parser.addDependentArgumentSet(provideBindDNAsUserIdentity, bindDNArgument);
1285
1286    parser.addExclusiveArgumentSet(newPassword, newPasswordFile,
1287         promptForNewPassword, generateClientSideNewPassword);
1288
1289    parser.addDependentArgumentSet(generatedPasswordLength,
1290         generateClientSideNewPassword);
1291    parser.addDependentArgumentSet(generatedPasswordCharacterSet,
1292         generateClientSideNewPassword);
1293
1294    parser.addExclusiveArgumentSet(currentPassword, currentPasswordFile,
1295         promptForCurrentPassword);
1296
1297    parser.addDependentArgumentSet(assuredReplicationLocalLevel,
1298         useAssuredReplication);
1299    parser.addDependentArgumentSet(assuredReplicationRemoteLevel,
1300         useAssuredReplication);
1301    parser.addDependentArgumentSet(assuredReplicationTimeout,
1302         useAssuredReplication);
1303
1304    parser.addExclusiveArgumentSet(retireCurrentPassword, purgeCurrentPassword);
1305  }
1306
1307
1308
1309  /**
1310   * {@inheritDoc}
1311   */
1312  @Override()
1313  @NotNull()
1314  protected Set<Character> getSuppressedShortIdentifiers()
1315  {
1316    return StaticUtils.setOf('N');
1317  }
1318
1319
1320
1321  /**
1322   * {@inheritDoc}
1323   */
1324  @Override()
1325  public void doExtendedNonLDAPArgumentValidation()
1326         throws ArgumentException
1327  {
1328    // Make sure that if any generate password character sets were provided,
1329    // they must all be non-empty.
1330    if (generatedPasswordCharacterSet.isPresent())
1331    {
1332      for (final String charSet : generatedPasswordCharacterSet.getValues())
1333      {
1334        if (charSet.isEmpty())
1335        {
1336          throw new ArgumentException(ERR_PWMOD_CHAR_SET_EMPTY.get(
1337               generatedPasswordCharacterSet.getIdentifierString()));
1338        }
1339      }
1340    }
1341  }
1342
1343
1344
1345  /**
1346   * {@inheritDoc}
1347   */
1348  @Override()
1349  @NotNull()
1350  public ResultCode doToolProcessing()
1351  {
1352    LDAPConnectionPool pool = null;
1353    try
1354    {
1355      // Create a connection pool that will be used to communicate with the
1356      // directory server.  If we should use an administrative session, then
1357      // create a connect processor that will be used to start the session
1358      // before performing the bind.
1359      try
1360      {
1361        final StartAdministrativeSessionPostConnectProcessor p;
1362        if (useAdministrativeSession.isPresent())
1363        {
1364          p = new StartAdministrativeSessionPostConnectProcessor(
1365               new StartAdministrativeSessionExtendedRequest(getToolName(),
1366                    true));
1367        }
1368        else
1369        {
1370          p = null;
1371        }
1372
1373        pool = getConnectionPool(1, 2, 0, p, null, true,
1374             new ReportBindResultLDAPConnectionPoolHealthCheck(this, true,
1375                  verbose.isPresent()));
1376
1377
1378        // Figure out the method to use to update the password.
1379        final String updateMethod;
1380        try
1381        {
1382          updateMethod = getPasswordUpdateMethod(pool);
1383        }
1384        catch (final LDAPException e)
1385        {
1386          Debug.debugException(e);
1387          logCompletionMessage(true, e.getMessage());
1388          return e.getResultCode();
1389        }
1390
1391
1392        switch (updateMethod)
1393        {
1394          case PASSWORD_CHANGE_METHOD_PW_MOD_EXTOP:
1395            return doPasswordModifyExtendedOperation(pool);
1396
1397          case PASSWORD_CHANGE_METHOD_AD:
1398            return doLDAPModifyPasswordUpdate(pool, true);
1399
1400          case PASSWORD_CHANGE_METHOD_LDAP_MOD:
1401          default:
1402            return doLDAPModifyPasswordUpdate(pool, false);
1403        }
1404      }
1405      catch (final LDAPException le)
1406      {
1407        Debug.debugException(le);
1408
1409        // Unable to create the connection pool, which means that either the
1410        // connection could not be established or the attempt to authenticate
1411        // the connection failed.  If the bind failed, then the report bind
1412        // result health check should have already reported the bind failure.
1413        // If the failure was something else, then display that failure result.
1414        if (le.getResultCode() != ResultCode.INVALID_CREDENTIALS)
1415        {
1416          for (final String line :
1417               ResultUtils.formatResult(le, true, 0, WRAP_COLUMN))
1418          {
1419            err(line);
1420          }
1421        }
1422        return le.getResultCode();
1423      }
1424    }
1425    finally
1426    {
1427      if (pool != null)
1428      {
1429        pool.close();
1430      }
1431    }
1432  }
1433
1434
1435
1436  /**
1437   * Determines the method that should be used to update the password.
1438   *
1439   * @param  pool  The connection pool to use to communicate with the
1440   *               directory server, if appropriate.
1441   *
1442   * @return  The method that should be used to update the password.  The value
1443   *          returned will be one of
1444   *          {@link #PASSWORD_CHANGE_METHOD_PW_MOD_EXTOP},
1445   *          {@link #PASSWORD_CHANGE_METHOD_LDAP_MOD}, or
1446   *          {@link #PASSWORD_CHANGE_METHOD_AD}.
1447   *
1448   * @throws  LDAPException  If a problem occurs while attempting to make the
1449   *                         determination.
1450   */
1451  @NotNull()
1452  private String getPasswordUpdateMethod(@NotNull final LDAPConnectionPool pool)
1453          throws LDAPException
1454  {
1455    if (passwordChangeMethod.isPresent())
1456    {
1457      switch (StaticUtils.toLowerCase(passwordChangeMethod.getValue()))
1458      {
1459        case PASSWORD_CHANGE_METHOD_PW_MOD_EXTOP:
1460          return PASSWORD_CHANGE_METHOD_PW_MOD_EXTOP;
1461        case PASSWORD_CHANGE_METHOD_LDAP_MOD:
1462          return PASSWORD_CHANGE_METHOD_LDAP_MOD;
1463        case PASSWORD_CHANGE_METHOD_AD:
1464          return PASSWORD_CHANGE_METHOD_AD;
1465      }
1466    }
1467
1468
1469    // Retrieve the root DSE from the directory server.  If we can't get the
1470    // root DSE, then default to the password modify extended operation.
1471    final RootDSE rootDSE;
1472    try
1473    {
1474      rootDSE = pool.getRootDSE();
1475    }
1476    catch (final LDAPException e)
1477    {
1478      Debug.debugException(e);
1479      return PASSWORD_CHANGE_METHOD_PW_MOD_EXTOP;
1480    }
1481
1482    if (rootDSE == null)
1483    {
1484      return PASSWORD_CHANGE_METHOD_PW_MOD_EXTOP;
1485    }
1486
1487
1488    // If the root DSE claims support for the password modify extended
1489    // operation, then use that method.
1490    if (rootDSE.supportsExtendedOperation(
1491         PasswordModifyExtendedRequest.PASSWORD_MODIFY_REQUEST_OID))
1492    {
1493      if (verbose.isPresent())
1494      {
1495        wrapOut(0, WRAP_COLUMN,
1496             INFO_PWMOD_SELECTING_PW_MOD_EXTOP_METHOD.get());
1497      }
1498
1499      return PASSWORD_CHANGE_METHOD_PW_MOD_EXTOP;
1500    }
1501
1502
1503    // We need to differentiate between Active Directory and other types of
1504    // servers.  Unfortunately, Active Directory doesn't seem to provide
1505    // vendorName or vendorVersion attributes in its root DSE, so we'll need to
1506    // use some other means of detecting it.  Let's assume that if the server
1507    // advertises support for at least twenty supported controls in Microsoft's
1508    // OID range (starting with 1.2.840.113556), then it's an Active Directory
1509    // instance.  At the time this was written, two different AD versions each
1510    // advertised support for nearly double that number.
1511    int numMicrosoftControlsSupported = 0;
1512    for (final String oid : rootDSE.getSupportedControlOIDs())
1513    {
1514      if (oid.startsWith(MICROSOFT_BASE_OBJECT_IDENTIFIER + '.'))
1515      {
1516        numMicrosoftControlsSupported++;
1517      }
1518    }
1519
1520    if (numMicrosoftControlsSupported >= 20)
1521    {
1522      if (verbose.isPresent())
1523      {
1524        wrapOut(0, WRAP_COLUMN,
1525             INFO_PWMOD_SELECTING_AD_METHOD_CONTROL_COUNT.get(
1526                  numMicrosoftControlsSupported,
1527                  MICROSOFT_BASE_OBJECT_IDENTIFIER));
1528      }
1529
1530      return PASSWORD_CHANGE_METHOD_AD;
1531    }
1532
1533
1534    // Fall back to a default of a regular LDAP modify operation.
1535    if (verbose.isPresent())
1536    {
1537      wrapOut(0, WRAP_COLUMN,
1538           INFO_PWMOD_DEFAULTING_TO_LDAP_MOD.get());
1539    }
1540
1541    return PASSWORD_CHANGE_METHOD_LDAP_MOD;
1542  }
1543
1544
1545
1546  /**
1547   * Attempts a password modify extended operation to change the target user's
1548   * password.
1549   *
1550   * @param  pool  A connection pool to use to communicate with the directory
1551   *               server.
1552   *
1553   * @return  A result code that indicates whether the password update was
1554   *          successful.
1555   */
1556  @NotNull()
1557  private ResultCode doPasswordModifyExtendedOperation(
1558                          @NotNull final LDAPConnectionPool pool)
1559  {
1560    // Create the password modify extended request to be processed.
1561    final String identity;
1562    final byte[] currentPW;
1563    final byte[] newPW;
1564    final Control[] controls;
1565    try
1566    {
1567      identity = getUserIdentity(null, false);
1568      currentPW = getCurrentPassword();
1569      newPW = getNewPassword();
1570      controls = getUpdateControls();
1571    }
1572    catch (final LDAPException e)
1573    {
1574      Debug.debugException(e);
1575      logCompletionMessage(true, e.getMessage());
1576      return e.getResultCode();
1577    }
1578
1579    final PasswordModifyExtendedRequest passwordModifyRequest =
1580         new PasswordModifyExtendedRequest(identity, currentPW, newPW,
1581              controls);
1582
1583
1584    // Send the request and interpret the response.
1585    LDAPConnection connection = null;
1586    try
1587    {
1588      connection = pool.getConnection();
1589      final PasswordModifyExtendedResult passwordModifyResult =
1590           (PasswordModifyExtendedResult)
1591           connection.processExtendedOperation(passwordModifyRequest);
1592
1593      out();
1594      out(INFO_PWMOD_EXTOP_RESULT_HEADER.get());
1595      for (final String line :
1596           ResultUtils.formatResult(passwordModifyResult, true, 0, WRAP_COLUMN))
1597      {
1598        out(line);
1599      }
1600      out();
1601
1602      final String generatedPassword =
1603           passwordModifyResult.getGeneratedPassword();
1604      if (passwordModifyResult.getResultCode() == ResultCode.SUCCESS)
1605      {
1606        logCompletionMessage(false, INFO_PWMOD_EXTOP_SUCCESSFUL.get());
1607        if (generatedPassword != null)
1608        {
1609          out();
1610          wrapOut(0, WRAP_COLUMN,
1611               INFO_PWMOD_SERVER_GENERATED_PW.get(generatedPassword));
1612        }
1613
1614        return ResultCode.SUCCESS;
1615      }
1616      else if (passwordModifyResult.getResultCode() == ResultCode.NO_OPERATION)
1617      {
1618        logCompletionMessage(false, INFO_PWMOD_EXTOP_NO_OP.get());
1619        if (generatedPassword != null)
1620        {
1621          out();
1622          wrapOut(0, WRAP_COLUMN,
1623               INFO_PWMOD_SERVER_GENERATED_PW.get(generatedPassword));
1624        }
1625
1626        return ResultCode.SUCCESS;
1627      }
1628      else
1629      {
1630        logCompletionMessage(true,
1631             ERR_PWMOD_EXTOP_FAILED.get(
1632                  String.valueOf(passwordModifyResult.getResultCode()),
1633                  passwordModifyResult.getDiagnosticMessage()));
1634        return passwordModifyResult.getResultCode();
1635      }
1636    }
1637    catch (final LDAPException e)
1638    {
1639      Debug.debugException(e);
1640
1641      err();
1642      err(INFO_PWMOD_EXTOP_RESULT_HEADER.get());
1643      for (final String line :
1644           ResultUtils.formatResult(e, true, 0, WRAP_COLUMN))
1645      {
1646        err(line);
1647      }
1648      err();
1649
1650      if (connection != null)
1651      {
1652        pool.releaseDefunctConnection(connection);
1653        connection = null;
1654      }
1655
1656      logCompletionMessage(true,
1657           ERR_PWMOD_EXTOP_ERROR.get(String.valueOf(e.getResultCode()),
1658                e.getMessage()));
1659      return e.getResultCode();
1660    }
1661    finally
1662    {
1663      if (connection != null)
1664      {
1665        pool.releaseConnection(connection);
1666      }
1667    }
1668  }
1669
1670
1671
1672  /**
1673   * Attempts a regular LDAP modify operation to change the target user's
1674   * password.
1675   *
1676   * @param  pool               A connection pool to use to communicate with the
1677   *                            directory server.
1678   * @param  isActiveDirectory  Indicates whether the target directory server
1679   *                            is believed to be an Active Directory instance.
1680   *
1681   * @return  A result code that indicates whether the password update was
1682   *          successful.
1683   */
1684  @NotNull()
1685  private ResultCode doLDAPModifyPasswordUpdate(
1686               @NotNull final LDAPConnectionPool pool,
1687               final boolean isActiveDirectory)
1688  {
1689    // Get the information to include in the password modify extended request.
1690    byte[] currentPW;
1691    byte[] newPW;
1692    final String identity;
1693    final Control[] controls;
1694    try
1695    {
1696      identity = getUserIdentity(pool, isActiveDirectory);
1697      currentPW = getCurrentPassword();
1698      newPW = getNewPassword();
1699      controls = getUpdateControls();
1700    }
1701    catch (final LDAPException e)
1702    {
1703      Debug.debugException(e);
1704      logCompletionMessage(true, e.getMessage());
1705      return e.getResultCode();
1706    }
1707
1708
1709    // If there is no new password, then fail.
1710    if (newPW == null)
1711    {
1712      logCompletionMessage(true,
1713           ERR_PWMOD_NO_NEW_PW_FOR_MODIFY.get(newPassword.getIdentifierString(),
1714                newPasswordFile.getIdentifierString(),
1715                promptForNewPassword.getIdentifierString(),
1716                generateClientSideNewPassword.getIdentifierString()));
1717      return ResultCode.PARAM_ERROR;
1718    }
1719
1720
1721    // Determine the name of the attribute to modify.
1722    final String passwordAttr;
1723    if (isActiveDirectory)
1724    {
1725      passwordAttr = AD_PASSWORD_ATTRIBUTE;
1726      currentPW = encodePasswordForActiveDirectory(currentPW);
1727      newPW = encodePasswordForActiveDirectory(newPW);
1728    }
1729    else
1730    {
1731      passwordAttr = passwordAttribute.getValue();
1732    }
1733
1734
1735    // Construct the modify request to send to the server.
1736    final ModifyRequest modifyRequest;
1737    if (currentPW == null)
1738    {
1739      modifyRequest = new ModifyRequest(identity,
1740           new Modification(ModificationType.REPLACE, passwordAttr, newPW));
1741    }
1742    else
1743    {
1744      modifyRequest = new ModifyRequest(identity,
1745           new Modification(ModificationType.DELETE, passwordAttr, currentPW),
1746           new Modification(ModificationType.ADD, passwordAttr, newPW));
1747    }
1748
1749    modifyRequest.setControls(controls);
1750
1751
1752    // Send the modify request and read the result.
1753    LDAPResult modifyResult;
1754    try
1755    {
1756      modifyResult = pool.modify(modifyRequest);
1757    }
1758    catch (final LDAPException e)
1759    {
1760      Debug.debugException(e);
1761      modifyResult = e.toLDAPResult();
1762    }
1763
1764
1765    out();
1766    out(INFO_PWMOD_MODIFY_RESULT_HEADER.get());
1767    for (final String line :
1768         ResultUtils.formatResult(modifyResult, true, 0, WRAP_COLUMN))
1769    {
1770      out(line);
1771    }
1772    out();
1773
1774    if (modifyResult.getResultCode() == ResultCode.SUCCESS)
1775    {
1776      logCompletionMessage(false, INFO_PWMOD_MODIFY_SUCCESSFUL.get());
1777      return ResultCode.SUCCESS;
1778    }
1779    else if (modifyResult.getResultCode() == ResultCode.NO_OPERATION)
1780    {
1781      logCompletionMessage(false, INFO_PWMOD_MODIFY_NO_OP.get());
1782      return ResultCode.SUCCESS;
1783    }
1784    else
1785    {
1786      logCompletionMessage(true,
1787           ERR_PWMOD_MODIFY_FAILED.get(
1788                String.valueOf(modifyResult.getResultCode()),
1789                modifyResult.getDiagnosticMessage()));
1790      return modifyResult.getResultCode();
1791    }
1792  }
1793
1794
1795
1796  /**
1797   *  Encodes the provided password in the form that is needed when changing a
1798   *  password in Active Directory.  The password must be surrounded in
1799   *  quotation marks and encoded as UTF-16 with little-Endian ordering.
1800   *
1801   * @param  pw  The password to be encoded.  It may optionally be {@code null}.
1802   *
1803   * @return  The encoded password.
1804   */
1805  @Nullable()
1806  static byte[] encodePasswordForActiveDirectory(@Nullable final byte[] pw)
1807  {
1808    if (pw == null)
1809    {
1810      return null;
1811    }
1812
1813    final String quotedPassword = '"' + StaticUtils.toUTF8String(pw) + '"';
1814    return quotedPassword.getBytes(StandardCharsets.UTF_16LE);
1815  }
1816
1817
1818
1819  /**
1820   * Retrieves the user identity for whom to update the password.
1821   *
1822   * @param  pool               A connection pool to use to communicate with the
1823   *                            directory server, if necessary.  This may be
1824   *                            {@code null} if only an explicitly provided user
1825   *                            identity should be used.  If it is
1826   *                            non-{@code null}, then an attempt will be made
1827   *                            to infer the correct value, and the value
1828   *                            returned will be a DN.
1829   * @param  isActiveDirectory  Indicates whether the target directory server
1830   *                            is believed to be an Active Directory instance.
1831   *
1832   * @return  The user identity for whom to update the password.
1833   *
1834   * @throws  LDAPException  If a problem occurs while attempting to obtain the
1835   *                         user identity.
1836   */
1837  @NotNull()
1838  private String getUserIdentity(@NotNull final LDAPConnectionPool pool,
1839                                 final boolean isActiveDirectory)
1840          throws LDAPException
1841  {
1842    String identity = null;
1843    final DNArgument bindDNArgument =
1844         argumentParser.getDNArgument(BIND_DN_ARGUMENT_LONG_IDENTIFIER);
1845    if (userIdentity.isPresent())
1846    {
1847      identity = userIdentity.getValue();
1848    }
1849    else if (provideBindDNAsUserIdentity.isPresent())
1850    {
1851      identity = bindDNArgument.getStringValue();
1852      if ((pool == null) && verbose.isPresent())
1853      {
1854        out();
1855        wrapOut(0, WRAP_COLUMN,
1856             INFO_PWMOD_USING_USER_IDENTITY_FROM_DN_FOR_EXTOP.get(identity));
1857      }
1858    }
1859    else
1860    {
1861      if ((pool == null) && verbose.isPresent())
1862      {
1863        out();
1864        wrapOut(0, WRAP_COLUMN,
1865             INFO_PWMOD_OMITTING_USER_IDENTITY_FROM_EXTOP.get());
1866      }
1867    }
1868
1869    if (pool == null)
1870    {
1871      return identity;
1872    }
1873
1874    if (identity == null)
1875    {
1876      if (bindDNArgument.isPresent())
1877      {
1878        final DN bindDN = bindDNArgument.getValue();
1879        if (! bindDN.isNullDN())
1880        {
1881          return bindDN.toString();
1882        }
1883      }
1884
1885      final WhoAmIExtendedRequest whoAmIRequest = new WhoAmIExtendedRequest();
1886      try
1887      {
1888        final WhoAmIExtendedResult whoAmIResult = (WhoAmIExtendedResult)
1889             pool.processExtendedOperation(whoAmIRequest);
1890        if (whoAmIResult.getResultCode() == ResultCode.SUCCESS)
1891        {
1892          identity = whoAmIResult.getAuthorizationID();
1893        }
1894      }
1895      catch (final LDAPException e)
1896      {
1897        Debug.debugException(e);
1898      }
1899    }
1900
1901    if (identity == null)
1902    {
1903      throw new LDAPException(ResultCode.PARAM_ERROR,
1904           ERR_PWMOD_CANNOT_DETERMINE_USER_IDENTITY.get(
1905                userIdentity.getIdentifierString()));
1906    }
1907
1908
1909    final String userDN;
1910    final String lowerIdentity = StaticUtils.toLowerCase(identity);
1911    if (lowerIdentity.startsWith("dn:"))
1912    {
1913      userDN = identity.substring(3).trim();
1914    }
1915    else if (lowerIdentity.startsWith("u:"))
1916    {
1917      final String username = identity.substring(2).trim();
1918      if (username.isEmpty())
1919      {
1920        throw new LDAPException(ResultCode.PARAM_ERROR,
1921             ERR_PWMOD_USER_IDENTITY_EMPTY_USERNAME.get(
1922                  userIdentity.getIdentifierString()));
1923      }
1924
1925      userDN = searchForUser(pool, username, isActiveDirectory);
1926    }
1927    else
1928    {
1929      userDN = identity;
1930    }
1931
1932    final DN parsedUserDN;
1933    try
1934    {
1935      parsedUserDN = new DN(userDN);
1936    }
1937    catch (final LDAPException e)
1938    {
1939      Debug.debugException(e);
1940      throw new LDAPException(ResultCode.PARAM_ERROR,
1941           ERR_PWMOD_USER_IDENTITY_NOT_VALID_DN.get(userDN,
1942                userIdentity.getIdentifierString()),
1943           e);
1944    }
1945
1946    if (parsedUserDN.isNullDN())
1947    {
1948      throw new LDAPException(ResultCode.PARAM_ERROR,
1949           ERR_PWMOD_USER_IDENTITY_EMPTY_DN.get(
1950                userIdentity.getIdentifierString()));
1951    }
1952
1953    if (verbose.isPresent())
1954    {
1955      out();
1956      INFO_PWMOD_USER_IDENTITY_DN_FOR_MOD.get(userDN);
1957    }
1958
1959    return userDN;
1960  }
1961
1962
1963
1964  /**
1965   * Performs a search to determine the DN for the user with the given username.
1966   *
1967   * @param  pool               A connection pool to use to communicate with the
1968   *                            directory server.  It must not be {@code null}.
1969   * @param  username           The username for the target user.  It must not
1970   *                            be {@code null}.
1971   * @param  isActiveDirectory  Indicates whether the target directory server
1972   *                            is believed to be an Active Directory instance.
1973   *
1974   * @return  The DN for the user with the given username.
1975   *
1976   * @throws  LDAPException  If a problem occurs while searching for the user,
1977   *                         or if the search does not match exactly one user.
1978   */
1979  @NotNull()
1980  private String searchForUser(@NotNull final LDAPConnectionPool pool,
1981                               @NotNull final String username,
1982                               final boolean isActiveDirectory)
1983          throws LDAPException
1984  {
1985    // Construct the filter to use for the search.
1986    final List<String> filterAttributeNames;
1987    if (usernameAttribute.isPresent())
1988    {
1989      filterAttributeNames = usernameAttribute.getValues();
1990    }
1991    else if (isActiveDirectory)
1992    {
1993      filterAttributeNames = AD_USERNAME_ATTRIBUTES;
1994    }
1995    else
1996    {
1997      filterAttributeNames = DEFAULT_USERNAME_ATTRIBUTES;
1998    }
1999
2000    final Filter filter;
2001    if (filterAttributeNames.size() == 1)
2002    {
2003      filter = Filter.createEqualityFilter(filterAttributeNames.get(0),
2004           username);
2005    }
2006    else
2007    {
2008      final List<Filter> orComponents =
2009           new ArrayList<>(filterAttributeNames.size());
2010      for (final String attrName : filterAttributeNames)
2011      {
2012        orComponents.add(Filter.createEqualityFilter(attrName, username));
2013      }
2014
2015      filter = Filter.createORFilter(orComponents);
2016    }
2017
2018
2019    // Create the search request to use to find the target user entry.
2020    final SearchRequest searchRequest = new SearchRequest(
2021         searchBaseDN.getStringValue(), SearchScope.SUB, filter,
2022         SearchRequest.NO_ATTRIBUTES);
2023    searchRequest.setSizeLimit(1);
2024
2025    if (verbose.isPresent())
2026    {
2027      out();
2028      wrapOut(0, WRAP_COLUMN,
2029           INFO_PWMOD_ISSUING_SEARCH_FOR_USER.get(
2030                String.valueOf(searchRequest), username));
2031    }
2032
2033
2034    // Issue the search and get the results.
2035    SearchResult searchResult;
2036    LDAPException searchException = null;
2037    try
2038    {
2039      searchResult = pool.search(searchRequest);
2040    }
2041    catch (final LDAPException e)
2042    {
2043      Debug.debugException(e);
2044      searchException = e;
2045      searchResult = new SearchResult(e);
2046    }
2047
2048    if (verbose.isPresent())
2049    {
2050      out();
2051      for (final String line :
2052           ResultUtils.formatResult(searchResult, true, 0, WRAP_COLUMN))
2053      {
2054        out(line);
2055      }
2056    }
2057
2058    if (searchResult.getResultCode() == ResultCode.SUCCESS)
2059    {
2060      if (searchResult.getEntryCount() == 1)
2061      {
2062        return searchResult.getSearchEntries().get(0).getDN();
2063      }
2064      else
2065      {
2066        throw new LDAPException(ResultCode.NO_RESULTS_RETURNED,
2067             ERR_PWMOD_SEARCH_FOR_USER_NO_MATCHES.get(username));
2068      }
2069    }
2070    else if (searchResult.getResultCode() == ResultCode.SIZE_LIMIT_EXCEEDED)
2071    {
2072      throw new LDAPException(ResultCode.SIZE_LIMIT_EXCEEDED,
2073           ERR_PWMOD_SEARCH_FOR_USER_MULTIPLE_MATCHES.get(username),
2074           searchException);
2075    }
2076    else
2077    {
2078      throw new LDAPException(searchResult.getResultCode(),
2079           ERR_PWMOD_SEARCH_FOR_USER_FAILED.get(username,
2080                String.valueOf(searchResult.getResultCode()),
2081                searchResult.getDiagnosticMessage()),
2082           searchException);
2083    }
2084  }
2085
2086
2087
2088  /**
2089   * Retrieves the bytes that comprise the current password for the user, if one
2090   * should be provided in the password update request.
2091   *
2092   * @return  The bytes that comprise the current password for the user, or
2093   *          {@code null} if none should be provided in the password update
2094   *          request.
2095   *
2096   * @throws  LDAPException  If a problem occurs while trying to obtain the
2097   *                         current password.
2098   */
2099  @Nullable()
2100  private byte[] getCurrentPassword()
2101          throws LDAPException
2102  {
2103    if (currentPassword.isPresent())
2104    {
2105      return StaticUtils.getBytes(currentPassword.getValue());
2106    }
2107    else if (currentPasswordFile.isPresent())
2108    {
2109      final File f = currentPasswordFile.getValue();
2110      try
2111      {
2112        final char[] currentPasswordChars =
2113             getPasswordFileReader().readPassword(f);
2114        return StaticUtils.getBytes(new String(currentPasswordChars));
2115      }
2116      catch (final LDAPException e)
2117      {
2118        Debug.debugException(e);
2119        throw new LDAPException(e.getResultCode(),
2120             ERR_PWMOD_CANNOT_READ_CURRENT_PW_FILE.get(f.getAbsolutePath(),
2121                  e.getMessage()),
2122             e);
2123      }
2124      catch (final Exception e)
2125      {
2126        Debug.debugException(e);
2127        throw new LDAPException(ResultCode.LOCAL_ERROR,
2128             ERR_PWMOD_CANNOT_READ_CURRENT_PW_FILE.get(f.getAbsolutePath(),
2129                  StaticUtils.getExceptionMessage(e)),
2130             e);
2131      }
2132    }
2133    else if (promptForCurrentPassword.isPresent())
2134    {
2135      while (true)
2136      {
2137        getOut().print(INFO_PWMOD_PROMPT_CURRENT_PW.get());
2138        try
2139        {
2140          final byte[] pwBytes = PasswordReader.readPassword();
2141          if ((pwBytes == null) || (pwBytes.length == 0))
2142          {
2143            err();
2144            wrapErr(0, WRAP_COLUMN, ERR_PWMOD_PW_EMPTY.get());
2145            err();
2146            continue;
2147          }
2148
2149          return pwBytes;
2150        }
2151        catch (final Exception e)
2152        {
2153          throw new LDAPException(ResultCode.LOCAL_ERROR,
2154               ERR_PWMOD_CANNOT_PROMPT_FOR_CURRENT_PW.get(
2155                    StaticUtils.getExceptionMessage(e)),
2156               e);
2157        }
2158      }
2159    }
2160    else
2161    {
2162      return null;
2163    }
2164  }
2165
2166
2167
2168  /**
2169   * Retrieves the bytes that comprise the new password for the user, if one
2170   * should be provided in the password update request.
2171   *
2172   * @return  The bytes that comprise the new password for the user, or
2173   *          {@code null} if none should be provided in the password update
2174   *          request.
2175   *
2176   * @throws  LDAPException  If a problem occurs while trying to obtain the new
2177   *                         password.
2178   */
2179  @Nullable()
2180  private byte[] getNewPassword()
2181          throws LDAPException
2182  {
2183    if (newPassword.isPresent())
2184    {
2185      return StaticUtils.getBytes(newPassword.getValue());
2186    }
2187    else if (newPasswordFile.isPresent())
2188    {
2189      final File f = newPasswordFile.getValue();
2190      try
2191      {
2192        final char[] newPasswordChars = getPasswordFileReader().readPassword(f);
2193        return StaticUtils.getBytes(new String(newPasswordChars));
2194      }
2195      catch (final LDAPException e)
2196      {
2197        Debug.debugException(e);
2198        throw new LDAPException(e.getResultCode(),
2199             ERR_PWMOD_CANNOT_READ_NEW_PW_FILE.get(f.getAbsolutePath(),
2200                  e.getMessage()),
2201             e);
2202      }
2203      catch (final Exception e)
2204      {
2205        Debug.debugException(e);
2206        throw new LDAPException(ResultCode.LOCAL_ERROR,
2207             ERR_PWMOD_CANNOT_READ_NEW_PW_FILE.get(f.getAbsolutePath(),
2208                  StaticUtils.getExceptionMessage(e)),
2209             e);
2210      }
2211    }
2212    else if (promptForNewPassword.isPresent())
2213    {
2214      while (true)
2215      {
2216        getOut().print(INFO_PWMOD_PROMPT_NEW_PW.get());
2217
2218        final byte[] pwBytes;
2219        try
2220        {
2221          pwBytes = PasswordReader.readPassword();
2222          if ((pwBytes == null) || (pwBytes.length == 0))
2223          {
2224            err();
2225            wrapErr(0, WRAP_COLUMN, ERR_PWMOD_PW_EMPTY.get());
2226            err();
2227            continue;
2228          }
2229
2230          getOut().print(INFO_PWMOD_CONFIRM_NEW_PW.get());
2231          final byte[] confirmBytes = PasswordReader.readPassword();
2232          if ((confirmBytes == null) ||
2233               (! Arrays.equals(pwBytes, confirmBytes)))
2234          {
2235            Arrays.fill(pwBytes, (byte) 0x00);
2236            Arrays.fill(confirmBytes, (byte) 0x00);
2237
2238            err();
2239            wrapErr(0, WRAP_COLUMN, ERR_PWMOD_NEW_PW_MISMATCH.get());
2240            err();
2241            continue;
2242          }
2243
2244          Arrays.fill(confirmBytes, (byte) 0x00);
2245          return pwBytes;
2246        }
2247        catch (final Exception e)
2248        {
2249          Debug.debugException(e);
2250          throw new LDAPException(ResultCode.LOCAL_ERROR,
2251               ERR_PWMOD_CANNOT_PROMPT_FOR_NEW_PW.get(
2252                    StaticUtils.getExceptionMessage(e)),
2253               e);
2254        }
2255      }
2256    }
2257    else if (generateClientSideNewPassword.isPresent())
2258    {
2259      return generatePassword();
2260    }
2261    else
2262    {
2263      return null;
2264    }
2265  }
2266
2267
2268
2269  /**
2270   * Generates a new password for the user.
2271   *
2272   * @return  The new password that was generated.
2273   */
2274  @NotNull()
2275  private byte[] generatePassword()
2276  {
2277    final int length = generatedPasswordLength.getValue();
2278    final StringBuilder generatedPassword = new StringBuilder(length);
2279
2280    final SecureRandom random = ThreadLocalSecureRandom.get();
2281    final StringBuilder allPasswordCharacters = new StringBuilder();
2282    for (final String charSet : generatedPasswordCharacterSet.getValues())
2283    {
2284      allPasswordCharacters.append(charSet);
2285
2286      // Pick one character at random from the provided set to include in the
2287      // password.
2288      generatedPassword.append(
2289           charSet.charAt(random.nextInt(charSet.length())));
2290    }
2291
2292
2293    // Choose as many additional characters (across all of the sets) as needed
2294    // to reach the desired length.
2295    while (generatedPassword.length() < length)
2296    {
2297      generatedPassword.append(allPasswordCharacters.charAt(
2298           random.nextInt(allPasswordCharacters.length())));
2299    }
2300
2301
2302    // Scramble the generated password.
2303    final StringBuilder scrambledPassword =
2304         new StringBuilder(generatedPassword.length());
2305    while (true)
2306    {
2307      if (generatedPassword.length() == 1)
2308      {
2309        scrambledPassword.append(generatedPassword.charAt(0));
2310        break;
2311      }
2312      else
2313      {
2314        final int pos = random.nextInt(generatedPassword.length());
2315        scrambledPassword.append(generatedPassword.charAt(pos));
2316        generatedPassword.deleteCharAt(pos);
2317      }
2318    }
2319
2320    final String scrambledPasswordString = scrambledPassword.toString();
2321    out();
2322    wrapOut(0, WRAP_COLUMN,
2323         INFO_PWMOD_CLIENT_SIDE_GEN_PW.get(getToolName(),
2324              scrambledPasswordString));
2325    return StaticUtils.getBytes(scrambledPasswordString);
2326  }
2327
2328
2329
2330  /**
2331   * Retrieves the controls that should be included in the password update
2332   * request.
2333   *
2334   * @return  The controls that should be included in the password update
2335   *          request, or an empty array if no controls should be included.
2336   *
2337   * @throws  LDAPException  If a problem occurs while trying to create any of
2338   *                         the controls.
2339   */
2340  @NotNull()
2341  private Control[] getUpdateControls()
2342          throws LDAPException
2343  {
2344    final List<Control> controls = new ArrayList<>();
2345
2346    if (updateControl.isPresent())
2347    {
2348      controls.addAll(updateControl.getValues());
2349    }
2350
2351    if (usePasswordPolicyControlOnUpdate.isPresent())
2352    {
2353      controls.add(new PasswordPolicyRequestControl());
2354    }
2355
2356    if (noOperation.isPresent())
2357    {
2358      controls.add(new NoOpRequestControl());
2359    }
2360
2361    if (getPasswordValidationDetails.isPresent())
2362    {
2363      controls.add(new PasswordValidationDetailsRequestControl());
2364    }
2365
2366    if (retireCurrentPassword.isPresent())
2367    {
2368      controls.add(new RetirePasswordRequestControl(false));
2369    }
2370
2371    if (purgeCurrentPassword.isPresent())
2372    {
2373      controls.add(new PurgePasswordRequestControl(false));
2374    }
2375
2376    if (passwordUpdateBehavior.isPresent())
2377    {
2378      controls.add(LDAPModify.createPasswordUpdateBehaviorRequestControl(
2379           passwordUpdateBehavior.getIdentifierString(),
2380           passwordUpdateBehavior.getValues()));
2381    }
2382
2383    if (operationPurpose.isPresent())
2384    {
2385      controls.add(new OperationPurposeRequestControl(false, getToolName(),
2386           getToolVersion(),
2387           LDAPPasswordModify.class.getName() + ".getUpdateControls",
2388           operationPurpose.getValue()));
2389    }
2390
2391    if (useAssuredReplication.isPresent())
2392    {
2393      AssuredReplicationLocalLevel localLevel = null;
2394      if (assuredReplicationLocalLevel.isPresent())
2395      {
2396        final String level = assuredReplicationLocalLevel.getValue();
2397        if (level.equalsIgnoreCase(ASSURED_REPLICATION_LOCAL_LEVEL_NONE))
2398        {
2399          localLevel = AssuredReplicationLocalLevel.NONE;
2400        }
2401        else if (level.equalsIgnoreCase(
2402             ASSURED_REPLICATION_LOCAL_LEVEL_RECEIVED_ANY_SERVER))
2403        {
2404          localLevel = AssuredReplicationLocalLevel.RECEIVED_ANY_SERVER;
2405        }
2406        else if (level.equalsIgnoreCase(
2407             ASSURED_REPLICATION_LOCAL_LEVEL_PROCESSED_ALL_SERVERS))
2408        {
2409          localLevel = AssuredReplicationLocalLevel.PROCESSED_ALL_SERVERS;
2410        }
2411      }
2412
2413      AssuredReplicationRemoteLevel remoteLevel = null;
2414      if (assuredReplicationRemoteLevel.isPresent())
2415      {
2416        final String level = assuredReplicationRemoteLevel.getValue();
2417        if (level.equalsIgnoreCase(ASSURED_REPLICATION_REMOTE_LEVEL_NONE))
2418        {
2419          remoteLevel = AssuredReplicationRemoteLevel.NONE;
2420        }
2421        else if (level.equalsIgnoreCase(
2422             ASSURED_REPLICATION_REMOTE_LEVEL_RECEIVED_ANY_REMOTE_LOCATION))
2423        {
2424          remoteLevel =
2425               AssuredReplicationRemoteLevel.RECEIVED_ANY_REMOTE_LOCATION;
2426        }
2427        else if (level.equalsIgnoreCase(
2428             ASSURED_REPLICATION_REMOTE_LEVEL_RECEIVED_ALL_REMOTE_LOCATIONS))
2429        {
2430          remoteLevel =
2431               AssuredReplicationRemoteLevel.RECEIVED_ALL_REMOTE_LOCATIONS;
2432        }
2433        else if (level.equalsIgnoreCase(
2434             ASSURED_REPLICATION_REMOTE_LEVEL_PROCESSED_ALL_REMOTE_SERVERS))
2435        {
2436          remoteLevel =
2437               AssuredReplicationRemoteLevel.PROCESSED_ALL_REMOTE_SERVERS;
2438        }
2439      }
2440
2441      Long timeoutMillis = null;
2442      if (assuredReplicationTimeout.isPresent())
2443      {
2444        timeoutMillis =
2445             assuredReplicationTimeout.getValue(TimeUnit.MILLISECONDS);
2446      }
2447
2448      controls.add(new AssuredReplicationRequestControl(true, localLevel,
2449           localLevel, remoteLevel, remoteLevel, timeoutMillis, false));
2450    }
2451
2452
2453    return controls.toArray(StaticUtils.NO_CONTROLS);
2454  }
2455
2456
2457
2458  /**
2459   * Writes the provided message and sets it as the completion message.
2460   *
2461   * @param  isError  Indicates whether the message should be written to
2462   *                  standard error rather than standard output.
2463   * @param  message  The message to be written.
2464   */
2465  private void logCompletionMessage(final boolean isError,
2466                                    @NotNull final String message)
2467  {
2468    completionMessage.compareAndSet(null, message);
2469
2470    if (isError)
2471    {
2472      wrapErr(0, WRAP_COLUMN, message);
2473    }
2474    else
2475    {
2476      wrapOut(0, WRAP_COLUMN, message);
2477    }
2478  }
2479
2480
2481
2482  /**
2483   * {@inheritDoc}
2484   */
2485  @Override()
2486  public void handleUnsolicitedNotification(
2487                   @NotNull final LDAPConnection connection,
2488                   @NotNull final ExtendedResult notification)
2489  {
2490    final ArrayList<String> lines = new ArrayList<>(10);
2491    ResultUtils.formatUnsolicitedNotification(lines, notification, true, 0,
2492         WRAP_COLUMN);
2493    for (final String line : lines)
2494    {
2495      err(line);
2496    }
2497    err();
2498  }
2499
2500
2501
2502  /**
2503   * {@inheritDoc}
2504   */
2505  @Override()
2506  @NotNull()
2507  public LinkedHashMap<String[],String> getExampleUsages()
2508  {
2509    final LinkedHashMap<String[],String> examples = new LinkedHashMap<>();
2510
2511    examples.put(
2512         new String[]
2513         {
2514           "--hostname", "ds.example.com",
2515           "--port", "636",
2516           "--useSSL",
2517           "--userIdentity", "u:jdoe",
2518           "--promptForCurrentPassword",
2519           "--promptForNewPassword"
2520         },
2521         INFO_PWMOD_EXAMPLE_1.get());
2522
2523    examples.put(
2524         new String[]
2525         {
2526           "--hostname", "ds.example.com",
2527           "--port", "636",
2528           "--useSSL",
2529           "--bindDN", "uid=admin,dc=example,dc=com",
2530           "--bindPasswordFile", "admin-password.txt",
2531           "--userIdentity", "uid=jdoe,ou=People,dc=example,dc=com",
2532           "--generateClientSideNewPassword",
2533           "--passwordChangeMethod", "ldap-modify"
2534         },
2535         INFO_PWMOD_EXAMPLE_2.get());
2536
2537    return examples;
2538  }
2539}