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 org.forgerock.opendj.ldap.Connection;
030import org.forgerock.opendj.ldap.DN;
031import org.forgerock.opendj.ldap.LdapException;
032import org.forgerock.opendj.ldap.LDAPConnectionFactory;
033import org.forgerock.opendj.ldap.LDAPOptions;
034import org.forgerock.opendj.ldap.ModificationType;
035import org.forgerock.opendj.ldap.ResultCode;
036import org.forgerock.opendj.ldap.SSLContextBuilder;
037import org.forgerock.opendj.ldap.TrustManagers;
038import org.forgerock.opendj.ldap.requests.ModifyRequest;
039import org.forgerock.opendj.ldap.requests.Requests;
040
041import javax.net.ssl.SSLContext;
042import java.nio.charset.Charset;
043import java.security.GeneralSecurityException;
044
045/**
046 * This command-line client demonstrates how to reset a user password in
047 * Microsoft Active Directory.
048 * <br>
049 * The client takes as arguments the host and port of the Active Directory
050 * server, a flag indicating whether this is a self-reset (user changing own
051 * password) or an administrative reset (administrator changing a password),
052 * the DN and password of the user performing the reset, and target user DN
053 * and new user password.
054 */
055public final class PasswordResetForAD {
056
057    /**
058     * Reset a user password in Microsoft Active Directory.
059     * <br>
060     * The connection should be LDAPS, not LDAP, in order to perform the
061     * modification.
062     *
063     * @param args The command line arguments: host, port, "admin"|"self",
064     *             DN, password, targetDN, newPassword
065     */
066    public static void main(final String[] args) {
067        // --- JCite main ---
068        if (args.length != 7) {
069            System.err.println("Usage: host port \"admin\"|\"self\" DN "
070                    + "password targetDN newPassword");
071            System.err.println("For example: ad.example.com 636 admin "
072                    + "cn=administrator,cn=Users,DC=ad,DC=example,DC=com "
073                    + "Secret123 cn=testuser,cn=Users,DC=ad,DC=example,DC=com "
074                    + "NewP4s5w0rd");
075            System.exit(1);
076        }
077        final String host = args[0];
078        final int port = Integer.parseInt(args[1]);
079        final String mode = args[2];
080        final String bindDN = args[3];
081        final String bindPassword = args[4];
082        final String targetDN = args[5];
083        final String newPassword = args[6];
084
085        Connection connection = null;
086        try {
087            final LDAPConnectionFactory factory =
088                    new LDAPConnectionFactory(host, port, getTrustAllOptions());
089            connection = factory.getConnection();
090            connection.bind(bindDN, bindPassword.toCharArray());
091
092            ModifyRequest request =
093                    Requests.newModifyRequest(DN.valueOf(targetDN));
094            String passwordAttribute = "unicodePwd";
095
096            if ("admin".equalsIgnoreCase(mode)) {
097                // Request modify, replacing the password with the new.
098
099                request.addModification(
100                        ModificationType.REPLACE,
101                        passwordAttribute,
102                        encodePassword(newPassword)
103                );
104            } else if ("self".equalsIgnoreCase(mode)) {
105                // Request modify, deleting the old password, adding the new.
106
107                // The default password policy for Active Directory domain
108                // controller systems sets minimum password age to 1 (day).
109                // If you get a constraint violation error when trying this
110                // example, set this minimum password age to 0 by executing
111                // cmd.exe as Administrator and entering the following
112                // command at the prompt:
113                //
114                // net accounts /MINPWAGE:0
115
116                request.addModification(
117                        ModificationType.DELETE,
118                        passwordAttribute,
119                        encodePassword(bindPassword)
120                );
121                request.addModification(
122                        ModificationType.ADD,
123                        passwordAttribute,
124                        encodePassword(newPassword)
125                );
126            } else {
127                System.err.println("Mode must be admin or self, not " + mode);
128                System.exit(1);
129            }
130
131            connection.modify(request);
132
133            System.out.println("Successfully changed password for "
134                    + targetDN + " to " + newPassword + ".");
135        } catch (final LdapException e) {
136            System.err.println(e.getMessage());
137            System.exit(e.getResult().getResultCode().intValue());
138        } catch (final GeneralSecurityException e) {
139            System.err.println(e.getMessage());
140            System.exit(ResultCode.CLIENT_SIDE_CONNECT_ERROR.intValue());
141        } finally {
142            if (connection != null) {
143                connection.close();
144            }
145        }
146        // --- JCite main ---
147    }
148
149    // --- JCite encodePassword ---
150    /**
151     * Encode new password in UTF-16LE format for use with Active Directory.
152     *
153     * @param password String representation of the password
154     * @return Byte array containing encoded password
155     */
156    public static byte[] encodePassword(final String password) {
157        return ("\"" + password + "\"").getBytes(Charset.forName("UTF-16LE"));
158    }
159    // --- JCite encodePassword ---
160
161    /**
162     * For SSL the connection factory needs SSL context options. This
163     * implementation simply trusts all server certificates.
164     */
165    private static LDAPOptions getTrustAllOptions() throws GeneralSecurityException {
166        LDAPOptions lo = new LDAPOptions();
167        SSLContext sslContext =
168                new SSLContextBuilder().setTrustManager(TrustManagers.trustAll())
169                        .getSSLContext();
170        lo.setSSLContext(sslContext);
171        return lo;
172    }
173
174    /**
175     * Constructor not used.
176     */
177    private PasswordResetForAD() {
178        // Not used.
179    }
180}