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