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