/*
 * Copyright (c) 1999, 2011, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.jndi.ldap.sasl;

import javax.security.auth.callback.*;
import javax.security.sasl.RealmCallback;
import javax.security.sasl.RealmChoiceCallback;
import java.io.IOException;

/**
 * DefaultCallbackHandler for satisfying NameCallback and
 * PasswordCallback for an LDAP client.
 * NameCallback is used for getting the authentication ID and is
 * gotten from the java.naming.security.principal property.
 * PasswordCallback is gotten from the java.naming.security.credentials
 * property and must be of type String, char[] or byte[].
 * If byte[], it is assumed to have UTF-8 encoding.
 *
 * If the caller of getPassword() will be using the password as
 * a byte array, then it should encode the char[] array returned by
 * getPassword() into a byte[] using UTF-8.
 *
 * @author Rosanna Lee
 */
final class DefaultCallbackHandler implements CallbackHandler {
    private char[] passwd;
    private String authenticationID;
    private String authRealm;

    DefaultCallbackHandler(String principal, Object cred, String realm)
        throws IOException {
        authenticationID = principal;
        authRealm = realm;
        if (cred instanceof String) {
            passwd = ((String)cred).toCharArray();
        } else if (cred instanceof char[]) {
            passwd = ((char[])cred).clone();
        } else if (cred != null) {
            // assume UTF-8 encoding
            String orig = new String((byte[])cred, "UTF8");
            passwd = orig.toCharArray();
        }
    }

    public void handle(Callback[] callbacks)
        throws IOException, UnsupportedCallbackException {
            for (int i = 0; i < callbacks.length; i++) {
                if (callbacks[i] instanceof NameCallback) {
                    ((NameCallback)callbacks[i]).setName(authenticationID);

                } else if (callbacks[i] instanceof PasswordCallback) {
                    ((PasswordCallback)callbacks[i]).setPassword(passwd);

                } else if (callbacks[i] instanceof RealmChoiceCallback) {
                    /* Deals with a choice of realms */
                    String[] choices =
                        ((RealmChoiceCallback)callbacks[i]).getChoices();
                    int selected = 0;

                    if (authRealm != null && authRealm.length() > 0) {
                        selected = -1; // no realm chosen
                        for (int j = 0; j < choices.length; j++) {
                            if (choices[j].equals(authRealm)) {
                                selected = j;
                            }
                        }
                        if (selected == -1) {
                            StringBuffer allChoices = new StringBuffer();
                            for (int j = 0; j <  choices.length; j++) {
                                allChoices.append(choices[j] + ",");
                            }
                            throw new IOException("Cannot match " +
                                "'java.naming.security.sasl.realm' property value, '" +
                                authRealm + "' with choices " + allChoices +
                                "in RealmChoiceCallback");
                        }
                    }

                    ((RealmChoiceCallback)callbacks[i]).setSelectedIndex(selected);

                } else if (callbacks[i] instanceof RealmCallback) {
                    /* 1 or 0 realms specified in challenge */
                    RealmCallback rcb = (RealmCallback) callbacks[i];
                    if (authRealm != null) {
                        rcb.setText(authRealm);  // Use what user supplied
                    } else {
                        String defaultRealm = rcb.getDefaultText();
                        if (defaultRealm != null) {
                            rcb.setText(defaultRealm); // Use what server supplied
                        } else {
                            rcb.setText("");  // Specify no realm
                        }
                    }
                } else {
                    throw new UnsupportedCallbackException(callbacks[i]);
                }
            }
    }

    void clearPassword() {
        if (passwd != null) {
            for (int i = 0; i < passwd.length; i++) {
                passwd[i] = '\0';
            }
            passwd = null;
        }
    }

    protected void finalize() throws Throwable {
        clearPassword();
    }
}
