/* **************************************************************************
 * $OpenLDAP: /com/novell/sasl/client/DigestChallenge.java,v 1.3 2005/01/17 15:00:54 sunilk Exp $
 *
 * Copyright (C) 2003 Novell, Inc. All Rights Reserved.
 *
 * THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND
 * TREATIES. USE, MODIFICATION, AND REDISTRIBUTION OF THIS WORK IS SUBJECT
 * TO VERSION 2.0.1 OF THE OPENLDAP PUBLIC LICENSE, A COPY OF WHICH IS
 * AVAILABLE AT HTTP://WWW.OPENLDAP.ORG/LICENSE.HTML OR IN THE FILE "LICENSE"
 * IN THE TOP-LEVEL DIRECTORY OF THE DISTRIBUTION. ANY USE OR EXPLOITATION
 * OF THIS WORK OTHER THAN AS AUTHORIZED IN VERSION 2.0.1 OF THE OPENLDAP
 * PUBLIC LICENSE, OR OTHER PRIOR WRITTEN CONSENT FROM NOVELL, COULD SUBJECT
 * THE PERPETRATOR TO CRIMINAL AND CIVIL LIABILITY.
 ******************************************************************************/
package com.novell.sasl.client;

import java.util.*;

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

/**
 * Implements the DigestChallenge class which will be used by the
 * DigestMD5SaslClient class
 */
class DigestChallenge extends Object {
    public static final int QOP_AUTH = 0x01;
    public static final int QOP_AUTH_INT = 0x02;
    public static final int QOP_AUTH_CONF = 0x04;
    public static final int QOP_UNRECOGNIZED = 0x08;

    private static final int CIPHER_3DES = 0x01;
    private static final int CIPHER_DES = 0x02;
    private static final int CIPHER_RC4_40 = 0x04;
    private static final int CIPHER_RC4 = 0x08;
    private static final int CIPHER_RC4_56 = 0x10;
    private static final int CIPHER_UNRECOGNIZED = 0x20;
    private static final int CIPHER_RECOGNIZED_MASK =
            CIPHER_3DES | CIPHER_DES | CIPHER_RC4_40 | CIPHER_RC4 | CIPHER_RC4_56;

    private ArrayList m_realms;
    private String m_nonce;
    private int m_qop;
    private boolean m_staleFlag;
    private int m_maxBuf;
    private String m_characterSet;
    private String m_algorithm;
    private int m_cipherOptions;

    DigestChallenge(
            byte[] challenge)
            throws SaslException {
        m_realms = new ArrayList(5);
        m_nonce = null;
        m_qop = 0;
        m_staleFlag = false;
        m_maxBuf = -1;
        m_characterSet = null;
        m_algorithm = null;
        m_cipherOptions = 0;

        DirectiveList dirList = new DirectiveList(challenge);
        try {
            dirList.parseDirectives();
            checkSemantics(dirList);
        } catch (SaslException e) {
        }
    }

    /**
     * Checks the semantics of the directives in the directive list as parsed
     * from the digest challenge byte array.
     *
     * @param dirList the list of directives parsed from the digest challenge
     * @throws SaslException If a semantic error occurs
     */
    void checkSemantics(
            DirectiveList dirList) throws SaslException {
        Iterator directives = dirList.getIterator();
        ParsedDirective directive;
        String name;

        while (directives.hasNext()) {
            directive = (ParsedDirective) directives.next();
            name = directive.getName();
            if (name.equals("realm"))
                handleRealm(directive);
            else if (name.equals("nonce"))
                handleNonce(directive);
            else if (name.equals("qop"))
                handleQop(directive);
            else if (name.equals("maxbuf"))
                handleMaxbuf(directive);
            else if (name.equals("charset"))
                handleCharset(directive);
            else if (name.equals("algorithm"))
                handleAlgorithm(directive);
            else if (name.equals("cipher"))
                handleCipher(directive);
            else if (name.equals("stale"))
                handleStale(directive);
        }

    /* post semantic check */
        if (-1 == m_maxBuf)
            m_maxBuf = 65536;

        if (m_qop == 0)
            m_qop = QOP_AUTH;
        else if ((m_qop & QOP_AUTH) != QOP_AUTH)
            throw new SaslException("Only qop-auth is supported by client");
        else if (((m_qop & QOP_AUTH_CONF) == QOP_AUTH_CONF) &&
                (0 == (m_cipherOptions & CIPHER_RECOGNIZED_MASK)))
            throw new SaslException("Invalid cipher options");
        else if (null == m_nonce)
            throw new SaslException("Missing nonce directive");
        else if (m_staleFlag)
            throw new SaslException("Unexpected stale flag");
        else if (null == m_algorithm)
            throw new SaslException("Missing algorithm directive");
    }

    /**
     * This function implements the semenatics of the nonce directive.
     *
     * @param pd ParsedDirective
     * @throws SaslException If an error occurs due to too many nonce
     *                       values
     */
    void handleNonce(
            ParsedDirective pd) throws SaslException {
        if (null != m_nonce)
            throw new SaslException("Too many nonce values.");

        m_nonce = pd.getValue();
    }

    /**
     * This function implements the semenatics of the realm directive.
     *
     * @param pd ParsedDirective
     */
    void handleRealm(
            ParsedDirective pd) {
        m_realms.add(pd.getValue());
    }

    /**
     * This function implements the semenatics of the qop (quality of protection)
     * directive. The value of the qop directive is as defined below:
     * qop-options =     "qop" "=" <"> qop-list <">
     * qop-list    =     1#qop-value
     * qop-value    =     "auth" | "auth-int"  | "auth-conf" | token
     *
     * @param pd ParsedDirective
     * @throws SaslException If an error occurs due to too many qop
     *                       directives
     */
    void handleQop(
            ParsedDirective pd) throws SaslException {
        String token;
        TokenParser parser;

        if (m_qop != 0)
            throw new SaslException("Too many qop directives.");

        parser = new TokenParser(pd.getValue());
        for (token = parser.parseToken();
             token != null;
             token = parser.parseToken()) {
            if (token.equals("auth"))
                m_qop |= QOP_AUTH;
            else if (token.equals("auth-int"))
                m_qop |= QOP_AUTH_INT;
            else if (token.equals("auth-conf"))
                m_qop |= QOP_AUTH_CONF;
            else
                m_qop |= QOP_UNRECOGNIZED;
        }
    }

    /**
     * This function implements the semenatics of the Maxbuf directive.
     * the value is defined as: 1*DIGIT
     *
     * @param pd ParsedDirective
     * @throws SaslException If an error occur
     */
    void handleMaxbuf(
            ParsedDirective pd) throws SaslException {
        if (-1 != m_maxBuf) /*it's initialized to -1 */
            throw new SaslException("Too many maxBuf directives.");

        m_maxBuf = Integer.parseInt(pd.getValue());

        if (0 == m_maxBuf)
            throw new SaslException("Max buf value must be greater than zero.");
    }

    /**
     * This function implements the semenatics of the charset directive.
     * the value is defined as: 1*DIGIT
     *
     * @param pd ParsedDirective
     * @throws SaslException If an error occurs dur to too many charset
     *                       directives or Invalid character encoding
     *                       directive
     */
    void handleCharset(
            ParsedDirective pd) throws SaslException {
        if (null != m_characterSet)
            throw new SaslException("Too many charset directives.");

        m_characterSet = pd.getValue();

        if (!m_characterSet.equals("utf-8"))
            throw new SaslException("Invalid character encoding directive");
    }

    /**
     * This function implements the semenatics of the charset directive.
     * the value is defined as: 1*DIGIT
     *
     * @param pd ParsedDirective
     * @throws SaslException If an error occurs due to too many algorith
     *                       directive or Invalid algorithm directive
     *                       value
     */
    void handleAlgorithm(
            ParsedDirective pd) throws SaslException {
        if (null != m_algorithm)
            throw new SaslException("Too many algorithm directives.");

        m_algorithm = pd.getValue();

        if (!"md5-sess".equals(m_algorithm))
            throw new SaslException("Invalid algorithm directive value: " +
                    m_algorithm);
    }

    /**
     * This function implements the semenatics of the cipher-opts directive
     * directive. The value of the qop directive is as defined below:
     * qop-options =     "qop" "=" <"> qop-list <">
     * qop-list    =     1#qop-value
     * qop-value    =     "auth" | "auth-int"  | "auth-conf" | token
     *
     * @param pd ParsedDirective
     * @throws SaslException If an error occurs due to Too many cipher
     *                       directives
     */
    void handleCipher(
            ParsedDirective pd) throws SaslException {
        String token;
        TokenParser parser;

        if (0 != m_cipherOptions)
            throw new SaslException("Too many cipher directives.");

        parser = new TokenParser(pd.getValue());
        token = parser.parseToken();
        for (token = parser.parseToken();
             token != null;
             token = parser.parseToken()) {
            if ("3des".equals(token))
                m_cipherOptions |= CIPHER_3DES;
            else if ("des".equals(token))
                m_cipherOptions |= CIPHER_DES;
            else if ("rc4-40".equals(token))
                m_cipherOptions |= CIPHER_RC4_40;
            else if ("rc4".equals(token))
                m_cipherOptions |= CIPHER_RC4;
            else if ("rc4-56".equals(token))
                m_cipherOptions |= CIPHER_RC4_56;
            else
                m_cipherOptions |= CIPHER_UNRECOGNIZED;
        }

        if (m_cipherOptions == 0)
            m_cipherOptions = CIPHER_UNRECOGNIZED;
    }

    /**
     * This function implements the semenatics of the stale directive.
     *
     * @param pd ParsedDirective
     * @throws SaslException If an error occurs due to Too many stale
     *                       directives or Invalid stale directive value
     */
    void handleStale(
            ParsedDirective pd) throws SaslException {
        if (false != m_staleFlag)
            throw new SaslException("Too many stale directives.");

        if ("true".equals(pd.getValue()))
            m_staleFlag = true;
        else
            throw new SaslException("Invalid stale directive value: " +
                    pd.getValue());
    }

    /**
     * Return the list of the All the Realms
     *
     * @return List of all the realms
     */
    public ArrayList getRealms() {
        return m_realms;
    }

    /**
     * @return Returns the Nonce
     */
    public String getNonce() {
        return m_nonce;
    }

    /**
     * Return the quality-of-protection
     *
     * @return The quality-of-protection
     */
    public int getQop() {
        return m_qop;
    }

    /**
     * @return The state of the Staleflag
     */
    public boolean getStaleFlag() {
        return m_staleFlag;
    }

    /**
     * @return The Maximum Buffer value
     */
    public int getMaxBuf() {
        return m_maxBuf;
    }

    /**
     * @return character set values as string
     */
    public String getCharacterSet() {
        return m_characterSet;
    }

    /**
     * @return The String value of the algorithm
     */
    public String getAlgorithm() {
        return m_algorithm;
    }

    /**
     * @return The cipher options
     */
    public int getCipherOptions() {
        return m_cipherOptions;
    }
}

