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