001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *      Copyright 2013-2015 ForgeRock AS.
024 *
025 */
026
027package org.forgerock.opendj.examples;
028
029import static org.forgerock.opendj.ldap.LDAPConnectionFactory.*;
030
031import org.forgerock.opendj.ldap.Connection;
032import org.forgerock.opendj.ldap.DN;
033import org.forgerock.opendj.ldap.LdapException;
034import org.forgerock.opendj.ldap.LDAPConnectionFactory;
035import org.forgerock.opendj.ldap.ModificationType;
036import org.forgerock.opendj.ldap.ResultCode;
037import org.forgerock.opendj.ldap.SSLContextBuilder;
038import org.forgerock.opendj.ldap.TrustManagers;
039import org.forgerock.opendj.ldap.requests.ModifyRequest;
040import org.forgerock.opendj.ldap.requests.Requests;
041import org.forgerock.util.Options;
042
043import javax.net.ssl.SSLContext;
044import java.nio.charset.Charset;
045import java.security.GeneralSecurityException;
046
047/**
048 * This command-line client demonstrates how to reset a user password in
049 * Microsoft Active Directory.
050 * <br>
051 * The client takes as arguments the host and port of the Active Directory
052 * server, a flag indicating whether this is a self-reset (user changing own
053 * password) or an administrative reset (administrator changing a password),
054 * the DN and password of the user performing the reset, and target user DN
055 * and new user password.
056 */
057public final class PasswordResetForAD {
058
059    /**
060     * Reset a user password in Microsoft Active Directory.
061     * <br>
062     * The connection should be LDAPS, not LDAP, in order to perform the
063     * modification.
064     *
065     * @param args The command line arguments: host, port, "admin"|"self",
066     *             DN, password, targetDN, newPassword
067     */
068    public static void main(final String[] args) {
069        // --- JCite main ---
070        if (args.length != 7) {
071            System.err.println("Usage: host port \"admin\"|\"self\" DN "
072                    + "password targetDN newPassword");
073            System.err.println("For example: ad.example.com 636 admin "
074                    + "cn=administrator,cn=Users,DC=ad,DC=example,DC=com "
075                    + "Secret123 cn=testuser,cn=Users,DC=ad,DC=example,DC=com "
076                    + "NewP4s5w0rd");
077            System.exit(1);
078        }
079        final String host = args[0];
080        final int port = Integer.parseInt(args[1]);
081        final String mode = args[2];
082        final String bindDN = args[3];
083        final String bindPassword = args[4];
084        final String targetDN = args[5];
085        final String newPassword = args[6];
086
087        Connection connection = null;
088        try {
089            final LDAPConnectionFactory factory =
090                    new LDAPConnectionFactory(host, port, getTrustAllOptions());
091            connection = factory.getConnection();
092            connection.bind(bindDN, bindPassword.toCharArray());
093
094            ModifyRequest request =
095                    Requests.newModifyRequest(DN.valueOf(targetDN));
096            String passwordAttribute = "unicodePwd";
097
098            if ("admin".equalsIgnoreCase(mode)) {
099                // Request modify, replacing the password with the new.
100
101                request.addModification(
102                        ModificationType.REPLACE,
103                        passwordAttribute,
104                        encodePassword(newPassword)
105                );
106            } else if ("self".equalsIgnoreCase(mode)) {
107                // Request modify, deleting the old password, adding the new.
108
109                // The default password policy for Active Directory domain
110                // controller systems sets minimum password age to 1 (day).
111                // If you get a constraint violation error when trying this
112                // example, set this minimum password age to 0 by executing
113                // cmd.exe as Administrator and entering the following
114                // command at the prompt:
115                //
116                // net accounts /MINPWAGE:0
117
118                request.addModification(
119                        ModificationType.DELETE,
120                        passwordAttribute,
121                        encodePassword(bindPassword)
122                );
123                request.addModification(
124                        ModificationType.ADD,
125                        passwordAttribute,
126                        encodePassword(newPassword)
127                );
128            } else {
129                System.err.println("Mode must be admin or self, not " + mode);
130                System.exit(1);
131            }
132
133            connection.modify(request);
134
135            System.out.println("Successfully changed password for "
136                    + targetDN + " to " + newPassword + ".");
137        } catch (final LdapException e) {
138            System.err.println(e.getMessage());
139            System.exit(e.getResult().getResultCode().intValue());
140        } catch (final GeneralSecurityException e) {
141            System.err.println(e.getMessage());
142            System.exit(ResultCode.CLIENT_SIDE_CONNECT_ERROR.intValue());
143        } finally {
144            if (connection != null) {
145                connection.close();
146            }
147        }
148        // --- JCite main ---
149    }
150
151    // --- JCite encodePassword ---
152    /**
153     * Encode new password in UTF-16LE format for use with Active Directory.
154     *
155     * @param password String representation of the password
156     * @return Byte array containing encoded password
157     */
158    public static byte[] encodePassword(final String password) {
159        return ("\"" + password + "\"").getBytes(Charset.forName("UTF-16LE"));
160    }
161    // --- JCite encodePassword ---
162
163    /**
164     * For SSL the connection factory needs SSL context options. This
165     * implementation simply trusts all server certificates.
166     */
167    private static Options getTrustAllOptions() throws GeneralSecurityException {
168        Options options = Options.defaultOptions();
169        SSLContext sslContext = new SSLContextBuilder()
170              .setTrustManager(TrustManagers.trustAll()).getSSLContext();
171        options.set(SSL_CONTEXT, sslContext);
172        return options;
173    }
174
175    /**
176     * Constructor not used.
177     */
178    private PasswordResetForAD() {
179        // Not used.
180    }
181}