/*
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
 */
package org.apache.qpid.management.common.sasl;

import java.io.*;

import org.apache.harmony.javax.security.auth.callback.*;
import org.apache.harmony.javax.security.sasl.*;

public class PlainSaslClient implements SaslClient {

    private boolean completed;
    private CallbackHandler cbh;
    private String authorizationID;
    private String authenticationID;
    private byte password[];
    private static byte SEPARATOR = 0;

    public PlainSaslClient(String authorizationID, CallbackHandler cbh) throws SaslException {
        completed = false;
        this.cbh = cbh;
        Object[] userInfo = getUserInfo();
        this.authorizationID = authorizationID;
        this.authenticationID = (String) userInfo[0];
        this.password = (byte[]) userInfo[1];
        if (authenticationID == null || password == null) {
            throw new SaslException("PLAIN: authenticationID and password must be specified");
        }
    }

    public byte[] evaluateChallenge(byte[] challenge) throws SaslException {
        if (completed) {
            throw new IllegalStateException("PLAIN: authentication already " +
                    "completed");
        }
        completed = true;
        try {
            byte authzid[] =
                    authorizationID == null ? null : authorizationID.getBytes("UTF8");
            byte authnid[] = authenticationID.getBytes("UTF8");
            byte response[] =
                    new byte[
                            password.length +
                                    authnid.length +
                                    2 + // SEPARATOR
                                    (authzid != null ? authzid.length : 0)
                            ];
            int size = 0;
            if (authzid != null) {
                System.arraycopy(authzid, 0, response, 0, authzid.length);
                size = authzid.length;
            }
            response[size++] = SEPARATOR;
            System.arraycopy(authnid, 0, response, size, authnid.length);
            size += authnid.length;
            response[size++] = SEPARATOR;
            System.arraycopy(password, 0, response, size, password.length);
            clearPassword();
            return response;
        } catch (UnsupportedEncodingException e) {
            throw new SaslException("PLAIN: Cannot get UTF-8 encoding of ids",
                    e);
        }
    }

    public String getMechanismName() {
        return "PLAIN";
    }

    public boolean hasInitialResponse() {
        return true;
    }

    public boolean isComplete() {
        return completed;
    }

    public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException {
        if (completed) {
            throw new IllegalStateException("PLAIN: this mechanism supports " +
                    "neither integrity nor privacy");
        } else {
            throw new IllegalStateException("PLAIN: authentication not " +
                    "completed");
        }
    }

    public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException {
        if (completed) {
            throw new IllegalStateException("PLAIN: this mechanism supports " +
                    "neither integrity nor privacy");
        } else {
            throw new IllegalStateException("PLAIN: authentication not " +
                    "completed");
        }
    }

    public Object getNegotiatedProperty(String propName) {
        if (completed) {
            if (propName.equals(Sasl.QOP)) {
                return "auth";
            } else {
                return null;
            }
        } else {
            throw new IllegalStateException("PLAIN: authentication not " +
                    "completed");
        }
    }

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

    public void dispose() throws SaslException {
        clearPassword();
    }

    protected void finalize() {
        clearPassword();
    }

    private Object[] getUserInfo() throws SaslException {
        try {
            final String userPrompt = "PLAIN authentication id: ";
            final String pwPrompt = "PLAIN password: ";
            NameCallback nameCb = new NameCallback(userPrompt);
            PasswordCallback passwordCb = new PasswordCallback(pwPrompt, false);
            cbh.handle(new Callback[]{nameCb, passwordCb});
            String userid = nameCb.getName();
            char pwchars[] = passwordCb.getPassword();
            byte pwbytes[];
            if (pwchars != null) {
                pwbytes = (new String(pwchars)).getBytes("UTF8");
                passwordCb.clearPassword();
            } else {
                pwbytes = null;
            }
            return (new Object[]{userid, pwbytes});
        } catch (IOException e) {
            throw new SaslException("Cannot get password", e);
        } catch (UnsupportedCallbackException e) {
            throw new SaslException("Cannot get userid/password", e);
        }
    }
}
