/*
 * Copyright (c) 2005, 2009, 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 sun.security.jgss.spnego;

import java.io.*;
import java.security.Provider;
import java.util.List;
import java.util.ArrayList;
import org.ietf.jgss.*;
import sun.security.jgss.*;
import sun.security.jgss.spi.*;
import sun.security.util.*;

/**
 * Implements the mechanism specific context class for SPNEGO
 * GSS-API mechanism
 *
 * @author Seema Malkani
 * @since 1.6
 */
public class SpNegoContext implements GSSContextSpi {

    /*
     * The different states that this context can be in.
     */
    private static final int STATE_NEW = 1;
    private static final int STATE_IN_PROCESS = 2;
    private static final int STATE_DONE = 3;
    private static final int STATE_DELETED = 4;

    private int state = STATE_NEW;

    /*
     * Optional features that the application can set and their default
     * values.
     */
    private boolean credDelegState = false;
    private boolean mutualAuthState = true;
    private boolean replayDetState = true;
    private boolean sequenceDetState = true;
    private boolean confState = true;
    private boolean integState = true;

    private GSSNameSpi peerName = null;
    private GSSNameSpi myName = null;
    private SpNegoCredElement myCred = null;

    private GSSContext mechContext = null;
    private byte[] DER_mechTypes = null;

    private int lifetime;
    private ChannelBinding channelBinding;
    private boolean initiator;

    // the underlying negotiated mechanism
    private Oid internal_mech = null;

    // the SpNegoMechFactory that creates this context
    final private SpNegoMechFactory factory;

    // debug property
    static final boolean DEBUG =
        java.security.AccessController.doPrivileged(
            new sun.security.action.GetBooleanAction
            ("sun.security.spnego.debug")).booleanValue();

    /**
     * Constructor for SpNegoContext to be called on the context initiator's
     * side.
     */
    public SpNegoContext(SpNegoMechFactory factory, GSSNameSpi peerName,
                        GSSCredentialSpi myCred,
                        int lifetime) throws GSSException {

        if (peerName == null)
            throw new IllegalArgumentException("Cannot have null peer name");
        if ((myCred != null) && !(myCred instanceof SpNegoCredElement)) {
            throw new IllegalArgumentException("Wrong cred element type");
        }
        this.peerName = peerName;
        this.myCred = (SpNegoCredElement) myCred;
        this.lifetime = lifetime;
        this.initiator = true;
        this.factory = factory;
    }

    /**
     * Constructor for SpNegoContext to be called on the context acceptor's
     * side.
     */
    public SpNegoContext(SpNegoMechFactory factory, GSSCredentialSpi myCred)
            throws GSSException {
        if ((myCred != null) && !(myCred instanceof SpNegoCredElement)) {
            throw new IllegalArgumentException("Wrong cred element type");
        }
        this.myCred = (SpNegoCredElement) myCred;
        this.initiator = false;
        this.factory = factory;
    }

    /**
     * Constructor for SpNegoContext to import a previously exported context.
     */
    public SpNegoContext(SpNegoMechFactory factory, byte [] interProcessToken)
        throws GSSException {
        throw new GSSException(GSSException.UNAVAILABLE,
                               -1, "GSS Import Context not available");
    }

    /**
     * Requests that confidentiality be available.
     */
    public final void requestConf(boolean value) throws GSSException {
        if (state == STATE_NEW && isInitiator())
            confState  = value;
    }

    /**
     * Is confidentiality available?
     */
    public final boolean getConfState() {
        return confState;
    }

    /**
     * Requests that integrity be available.
     */
    public final void requestInteg(boolean value) throws GSSException {
        if (state == STATE_NEW && isInitiator())
            integState  = value;
    }

    /**
     * Is integrity available?
     */
    public final boolean getIntegState() {
        return integState;
    }

    /**
     * Requests that credential delegation be done during context
     * establishment.
     */
    public final void requestCredDeleg(boolean value) throws GSSException {
        if (state == STATE_NEW && isInitiator())
            credDelegState  = value;
    }

    /**
     * Is credential delegation enabled?
     */
    public final boolean getCredDelegState() {
        if (mechContext != null &&
                (state == STATE_IN_PROCESS || state == STATE_DONE)) {
            return mechContext.getCredDelegState();
        } else {
            return credDelegState;
        }
    }

    /**
     * Requests that mutual authentication be done during context
     * establishment. Since this is fromm the client's perspective, it
     * essentially requests that the server be authenticated.
     */
    public final void requestMutualAuth(boolean value) throws GSSException {
        if (state == STATE_NEW && isInitiator()) {
            mutualAuthState  = value;
        }
    }

    /**
     * Is mutual authentication enabled? Since this is from the client's
     * perspective, it essentially meas that the server is being
     * authenticated.
     */
    public final boolean getMutualAuthState() {
        return mutualAuthState;
    }

    final void setCredDelegState(boolean state) {
        credDelegState = state;
    }

    final void setMutualAuthState(boolean state) {
        mutualAuthState = state;
    }

    final void setReplayDetState(boolean state) {
        replayDetState = state;
    }

    final void setSequenceDetState(boolean state) {
        sequenceDetState = state;
    }

    final void setConfState(boolean state) {
        confState = state;
    }

    final void setIntegState(boolean state) {
        integState = state;
    }

    /**
     * Returns the mechanism oid.
     *
     * @return the Oid of this context
     */
    public final Oid getMech() {
        if (isEstablished()) {
            return getNegotiatedMech();
        }
        return (SpNegoMechFactory.GSS_SPNEGO_MECH_OID);
    }

    public final Oid getNegotiatedMech() {
        return (internal_mech);
    }

    public final Provider getProvider() {
        return SpNegoMechFactory.PROVIDER;
    }

    public final void dispose() throws GSSException {
        mechContext = null;
        state = STATE_DELETED;
    }

    /**
     * Tests if this is the initiator side of the context.
     *
     * @return boolean indicating if this is initiator (true)
     *  or target (false)
     */
    public final boolean isInitiator() {
        return initiator;
    }

    /**
     * Tests if the context can be used for per-message service.
     * Context may allow the calls to the per-message service
     * functions before being fully established.
     *
     * @return boolean indicating if per-message methods can
     *  be called.
     */
    public final boolean isProtReady() {
        return (state == STATE_DONE);
    }

    /**
     * Initiator context establishment call. This method may be
     * required to be called several times. A CONTINUE_NEEDED return
     * call indicates that more calls are needed after the next token
     * is received from the peer.
     *
     * @param is contains the token received from the peer. On the
     *        first call it will be ignored.
     * @return any token required to be sent to the peer
     * It is responsibility of the caller to send the token
     * to its peer for processing.
     * @exception GSSException
     */
    public final byte[] initSecContext(InputStream is, int mechTokenSize)
        throws GSSException {

        byte[] retVal = null;
        NegTokenInit initToken = null;
        byte[] mechToken = null;
        int errorCode = GSSException.FAILURE;

        if (DEBUG) {
            System.out.println("Entered SpNego.initSecContext with " +
                                "state=" + printState(state));
        }
        if (!isInitiator()) {
            throw new GSSException(GSSException.FAILURE, -1,
                "initSecContext on an acceptor GSSContext");
        }

        try {
            if (state == STATE_NEW) {
                state = STATE_IN_PROCESS;

                errorCode = GSSException.NO_CRED;

                // determine available mech set
                Oid[] mechList = getAvailableMechs();
                DER_mechTypes = getEncodedMechs(mechList);

                // pull out first mechanism
                internal_mech = mechList[0];

                // get the token for first mechanism
                mechToken = GSS_initSecContext(null);

                errorCode = GSSException.DEFECTIVE_TOKEN;
                byte[] micToken = null;
                if (!GSSUtil.useMSInterop()) {
                    // calculate MIC only in normal mode
                    micToken = generateMechListMIC(DER_mechTypes);
                }
                // generate SPNEGO token
                initToken = new NegTokenInit(DER_mechTypes, getContextFlags(),
                                        mechToken, micToken);
                if (DEBUG) {
                    System.out.println("SpNegoContext.initSecContext: " +
                                "sending token of type = " +
                                SpNegoToken.getTokenName(initToken.getType()));
                }
                // get the encoded token
                retVal = initToken.getEncoded();

            } else if (state == STATE_IN_PROCESS) {

                errorCode = GSSException.FAILURE;
                if (is == null) {
                    throw new GSSException(errorCode, -1,
                                "No token received from peer!");
                }

                errorCode = GSSException.DEFECTIVE_TOKEN;
                byte[] server_token = new byte[is.available()];
                SpNegoToken.readFully(is, server_token);
                if (DEBUG) {
                    System.out.println("SpNegoContext.initSecContext: " +
                                        "process received token = " +
                                        SpNegoToken.getHexBytes(server_token));
                }

                // read the SPNEGO token
                // token will be validated when parsing
                NegTokenTarg targToken = new NegTokenTarg(server_token);

                if (DEBUG) {
                    System.out.println("SpNegoContext.initSecContext: " +
                                "received token of type = " +
                                SpNegoToken.getTokenName(targToken.getType()));
                }

                // pull out mechanism
                internal_mech = targToken.getSupportedMech();
                if (internal_mech == null) {
                    // return wth failure
                    throw new GSSException(errorCode, -1,
                                "supported mechansim from server is null");
                }

                // get the negotiated result
                SpNegoToken.NegoResult negoResult = null;
                int result = targToken.getNegotiatedResult();
                switch (result) {
                    case 0:
                        negoResult = SpNegoToken.NegoResult.ACCEPT_COMPLETE;
                        state = STATE_DONE;
                        break;
                    case 1:
                        negoResult = SpNegoToken.NegoResult.ACCEPT_INCOMPLETE;
                        state = STATE_IN_PROCESS;
                        break;
                    case 2:
                        negoResult = SpNegoToken.NegoResult.REJECT;
                        state = STATE_DELETED;
                        break;
                    default:
                        state = STATE_DONE;
                        break;
                }

                errorCode = GSSException.BAD_MECH;

                if (negoResult == SpNegoToken.NegoResult.REJECT) {
                    throw new GSSException(errorCode, -1,
                                        internal_mech.toString());
                }

                errorCode = GSSException.DEFECTIVE_TOKEN;

                if ((negoResult == SpNegoToken.NegoResult.ACCEPT_COMPLETE) ||
                    (negoResult == SpNegoToken.NegoResult.ACCEPT_INCOMPLETE)) {

                    // pull out the mechanism token
                    byte[] accept_token = targToken.getResponseToken();
                    if (accept_token == null) {
                        // return wth failure
                        throw new GSSException(errorCode, -1,
                                        "mechansim token from server is null");
                    }

                    mechToken = GSS_initSecContext(accept_token);

                    // verify MIC
                    if (!GSSUtil.useMSInterop()) {
                        byte[] micToken = targToken.getMechListMIC();
                        if (!verifyMechListMIC(DER_mechTypes, micToken)) {
                            throw new GSSException(errorCode, -1,
                                "verification of MIC on MechList Failed!");
                        }
                    }

                    if (isMechContextEstablished()) {
                        state = STATE_DONE;
                        retVal = mechToken;
                        if (DEBUG) {
                            System.out.println("SPNEGO Negotiated Mechanism = "
                                + internal_mech + " " +
                                GSSUtil.getMechStr(internal_mech));
                        }
                    } else {
                        // generate SPNEGO token
                        initToken = new NegTokenInit(null, null,
                                                mechToken, null);
                        if (DEBUG) {
                            System.out.println("SpNegoContext.initSecContext:" +
                                " continue sending token of type = " +
                                SpNegoToken.getTokenName(initToken.getType()));
                        }
                        // get the encoded token
                        retVal = initToken.getEncoded();
                    }
                }

            } else {
                // XXX Use logging API
                if (DEBUG) {
                    System.out.println(state);
                }
            }
            if (DEBUG) {
                if (retVal != null) {
                    System.out.println("SNegoContext.initSecContext: " +
                        "sending token = " + SpNegoToken.getHexBytes(retVal));
                }
            }
        } catch (GSSException e) {
            GSSException gssException =
                        new GSSException(errorCode, -1, e.getMessage());
            gssException.initCause(e);
            throw gssException;
        } catch (IOException e) {
            GSSException gssException =
                new GSSException(GSSException.FAILURE, -1, e.getMessage());
            gssException.initCause(e);
            throw gssException;
        }

        return retVal;
    }


    /**
     * Acceptor's context establishment call. This method may be
     * required to be called several times. A CONTINUE_NEEDED return
     * call indicates that more calls are needed after the next token
     * is received from the peer.
     *
     * @param is contains the token received from the peer.
     * @return any token required to be sent to the peer
     * It is responsibility of the caller to send the token
     * to its peer for processing.
     * @exception GSSException
     */
    public final byte[] acceptSecContext(InputStream is, int mechTokenSize)
        throws GSSException {

        byte[] retVal = null;
        SpNegoToken.NegoResult negoResult;
        boolean valid = true;

        if (DEBUG) {
            System.out.println("Entered SpNegoContext.acceptSecContext with " +
                               "state=" +  printState(state));
        }

        if (isInitiator()) {
            throw new GSSException(GSSException.FAILURE, -1,
                                   "acceptSecContext on an initiator " +
                                   "GSSContext");
        }
        try {
            if (state == STATE_NEW) {
                state = STATE_IN_PROCESS;

                // read data
                byte[] token = new byte[is.available()];
                SpNegoToken.readFully(is, token);
                if (DEBUG) {
                    System.out.println("SpNegoContext.acceptSecContext: " +
                                        "receiving token = " +
                                        SpNegoToken.getHexBytes(token));
                }

                // read the SPNEGO token
                // token will be validated when parsing
                NegTokenInit initToken = new NegTokenInit(token);

                if (DEBUG) {
                    System.out.println("SpNegoContext.acceptSecContext: " +
                                "received token of type = " +
                                SpNegoToken.getTokenName(initToken.getType()));
                }

                Oid[] mechList = initToken.getMechTypeList();
                DER_mechTypes = initToken.getMechTypes();
                if (DER_mechTypes == null) {
                    valid = false;
                }

                // get the mechanism token
                byte[] mechToken = initToken.getMechToken();

                /*
                 * Select the best match between the list of mechs
                 * that the initiator requested and the list that
                 * the acceptor will support.
                 */
                Oid[] supported_mechSet = getAvailableMechs();
                Oid mech_wanted =
                        negotiate_mech_type(supported_mechSet, mechList);
                if (mech_wanted == null) {
                    valid = false;
                }
                // save the desired mechansim
                internal_mech = mech_wanted;

                // get the token for mechanism
                byte[] accept_token = GSS_acceptSecContext(mechToken);
                if (accept_token == null) {
                    valid = false;
                }

                // verify MIC
                if (!GSSUtil.useMSInterop() && valid) {
                    valid = verifyMechListMIC(DER_mechTypes,
                                                initToken.getMechListMIC());
                }

                // determine negotiated result status
                if (valid) {
                    if (isMechContextEstablished()) {
                        negoResult = SpNegoToken.NegoResult.ACCEPT_COMPLETE;
                        state = STATE_DONE;
                        // now set the context flags for acceptor
                        setContextFlags();
                        // print the negotiated mech info
                        if (DEBUG) {
                            System.out.println("SPNEGO Negotiated Mechanism = "
                                + internal_mech + " " +
                                GSSUtil.getMechStr(internal_mech));
                        }
                    } else {
                        negoResult = SpNegoToken.NegoResult.ACCEPT_INCOMPLETE;
                        state = STATE_IN_PROCESS;
                    }
                } else {
                    negoResult = SpNegoToken.NegoResult.REJECT;
                    state = STATE_DONE;
                }

                if (DEBUG) {
                    System.out.println("SpNegoContext.acceptSecContext: " +
                                "mechanism wanted = " + mech_wanted);
                    System.out.println("SpNegoContext.acceptSecContext: " +
                                "negotiated result = " + negoResult);
                }

                // calculate MIC only in normal mode
                byte[] micToken = null;
                if (!GSSUtil.useMSInterop() && valid) {
                    micToken = generateMechListMIC(DER_mechTypes);
                }

                // generate SPNEGO token
                NegTokenTarg targToken = new NegTokenTarg(negoResult.ordinal(),
                                mech_wanted, accept_token, micToken);
                if (DEBUG) {
                    System.out.println("SpNegoContext.acceptSecContext: " +
                                "sending token of type = " +
                                SpNegoToken.getTokenName(targToken.getType()));
                }
                // get the encoded token
                retVal = targToken.getEncoded();

            } else if (state == STATE_IN_PROCESS) {
                // read the token
                byte[] client_token = new byte[is.available()];
                SpNegoToken.readFully(is, client_token);
                byte[] accept_token = GSS_acceptSecContext(client_token);
                if (accept_token == null) {
                    valid = false;
                }

                // determine negotiated result status
                if (valid) {
                    if (isMechContextEstablished()) {
                        negoResult = SpNegoToken.NegoResult.ACCEPT_COMPLETE;
                        state = STATE_DONE;
                    } else {
                        negoResult = SpNegoToken.NegoResult.ACCEPT_INCOMPLETE;
                        state = STATE_IN_PROCESS;
                    }
                } else {
                    negoResult = SpNegoToken.NegoResult.REJECT;
                    state = STATE_DONE;
                }

                // generate SPNEGO token
                NegTokenTarg targToken = new NegTokenTarg(negoResult.ordinal(),
                                null, accept_token, null);
                if (DEBUG) {
                    System.out.println("SpNegoContext.acceptSecContext: " +
                                "sending token of type = " +
                                SpNegoToken.getTokenName(targToken.getType()));
                }
                // get the encoded token
                retVal = targToken.getEncoded();

            } else {
                // XXX Use logging API
                if (DEBUG) {
                    System.out.println("AcceptSecContext: state = " + state);
                }
            }
            if (DEBUG) {
                    System.out.println("SpNegoContext.acceptSecContext: " +
                        "sending token = " + SpNegoToken.getHexBytes(retVal));
            }
        } catch (IOException e) {
            GSSException gssException =
                new GSSException(GSSException.FAILURE, -1, e.getMessage());
            gssException.initCause(e);
            throw gssException;
        }

        return retVal;
    }

    /**
     * obtain the available mechanisms
     */
    private Oid[] getAvailableMechs() {
        if (myCred != null) {
            Oid[] mechs = new Oid[1];
            mechs[0] = myCred.getInternalMech();
            return mechs;
        } else {
            return factory.availableMechs;
        }
    }

    /**
     * get ther DER encoded MechList
     */
    private byte[] getEncodedMechs(Oid[] mechSet)
        throws IOException, GSSException {

        DerOutputStream mech = new DerOutputStream();
        for (int i = 0; i < mechSet.length; i++) {
            byte[] mechType = mechSet[i].getDER();
            mech.write(mechType);
        }
        // insert in SEQUENCE
        DerOutputStream mechTypeList = new DerOutputStream();
        mechTypeList.write(DerValue.tag_Sequence, mech);
        byte[] encoded = mechTypeList.toByteArray();
        return encoded;
    }

    /**
     * get the context flags
     */
    private BitArray getContextFlags() {
        BitArray out = new BitArray(7);

        if (getCredDelegState()) out.set(0, true);
        if (getMutualAuthState()) out.set(1, true);
        if (getReplayDetState()) out.set(2, true);
        if (getSequenceDetState()) out.set(3, true);
        if (getConfState()) out.set(5, true);
        if (getIntegState()) out.set(6, true);

        return out;
    }

    private void setContextFlags() {

        if (mechContext != null) {
            // default for cred delegation is false
            if (mechContext.getCredDelegState()) {
                setCredDelegState(true);
            }
            // default for the following are true
            if (!mechContext.getMutualAuthState()) {
                setMutualAuthState(false);
            }
            if (!mechContext.getReplayDetState()) {
                setReplayDetState(false);
            }
            if (!mechContext.getSequenceDetState()) {
                setSequenceDetState(false);
            }
            if (!mechContext.getIntegState()) {
                setIntegState(false);
            }
            if (!mechContext.getConfState()) {
                setConfState(false);
            }
        }
    }

    /**
     * generate MIC on mechList
     */
    private byte[] generateMechListMIC(byte[] mechTypes)
        throws GSSException {

        // sanity check the required input
        if (mechTypes == null) {
            if (DEBUG) {
                System.out.println("SpNegoContext: no MIC token included");
            }
            return null;
        }

        // check if mechansim supports integrity
        if (!mechContext.getIntegState()) {
            if (DEBUG) {
                System.out.println("SpNegoContext: no MIC token included" +
                        " - mechanism does not support integrity");
            }
            return null;
        }

        // compute MIC on DER encoded mechanism list
        byte[] mic = null;
        try {
            MessageProp prop = new MessageProp(0, true);
            mic = getMIC(mechTypes, 0, mechTypes.length, prop);
            if (DEBUG) {
                System.out.println("SpNegoContext: getMIC = " +
                                        SpNegoToken.getHexBytes(mic));
            }
        } catch (GSSException e) {
            mic = null;
            if (DEBUG) {
                System.out.println("SpNegoContext: no MIC token included" +
                        " - getMIC failed : " + e.getMessage());
            }
        }
        return mic;
    }

    /**
     * verify MIC on MechList
     */
    private boolean verifyMechListMIC(byte[] mechTypes, byte[] token)
        throws GSSException {

        // sanity check the input
        if (token == null) {
            if (DEBUG) {
                System.out.println("SpNegoContext: no MIC token validation");
            }
            return true;
        }

        // check if mechansim supports integrity
        if (!mechContext.getIntegState()) {
            if (DEBUG) {
                System.out.println("SpNegoContext: no MIC token validation" +
                        " - mechanism does not support integrity");
            }
            return true;
        }

        // now verify the token
        boolean valid = false;
        try {
            MessageProp prop = new MessageProp(0, true);
            verifyMIC(token, 0, token.length, mechTypes,
                        0, mechTypes.length, prop);
            valid = true;
        } catch (GSSException e) {
            valid = false;
            if (DEBUG) {
                System.out.println("SpNegoContext: MIC validation failed! " +
                                        e.getMessage());
            }
        }
        return valid;
    }

    /**
     * call gss_init_sec_context for the corresponding underlying mechanism
     */
    private byte[] GSS_initSecContext(byte[] token) throws GSSException {
        byte[] tok = null;

        if (mechContext == null) {
            // initialize mech context
            GSSName serverName =
                factory.manager.createName(peerName.toString(),
                    peerName.getStringNameType(), internal_mech);
            GSSCredential cred = null;
            if (myCred != null) {
                // create context with provided credential
                cred = new GSSCredentialImpl(factory.manager,
                    myCred.getInternalCred());
            }
            mechContext =
                factory.manager.createContext(serverName,
                    internal_mech, cred, GSSContext.DEFAULT_LIFETIME);
            mechContext.requestConf(confState);
            mechContext.requestInteg(integState);
            mechContext.requestCredDeleg(credDelegState);
            mechContext.requestMutualAuth(mutualAuthState);
            mechContext.requestReplayDet(replayDetState);
            mechContext.requestSequenceDet(sequenceDetState);
        }

        // pass token
        if (token != null) {
            tok = token;
        } else {
            tok = new byte[0];
        }

        // pass token to mechanism initSecContext
        byte[] init_token = mechContext.initSecContext(tok, 0, tok.length);

        return init_token;
    }

    /**
     * call gss_accept_sec_context for the corresponding underlying mechanism
     */
    private byte[] GSS_acceptSecContext(byte[] token) throws GSSException {

        if (mechContext == null) {
            // initialize mech context
            GSSCredential cred = null;
            if (myCred != null) {
                // create context with provided credential
                cred = new GSSCredentialImpl(factory.manager,
                myCred.getInternalCred());
            }
            mechContext =
                factory.manager.createContext(cred);
        }

        // pass token to mechanism acceptSecContext
        byte[] accept_token =
                mechContext.acceptSecContext(token, 0, token.length);

        return accept_token;
    }

    /**
     * This routine compares the recieved mechset to the mechset that
     * this server can support. It looks sequentially through the mechset
     * and the first one that matches what the server can support is
     * chosen as the negotiated mechanism. If one is found, negResult
     * is set to ACCEPT_COMPLETE, otherwise we return NULL and negResult
     * is set to REJECT.
     */
    private static Oid negotiate_mech_type(Oid[] supported_mechSet,
                                        Oid[] mechSet) {
        for (int i = 0; i < supported_mechSet.length; i++) {
            for (int j = 0; j < mechSet.length; j++) {
                if (mechSet[j].equals(supported_mechSet[i])) {
                    if (DEBUG) {
                        System.out.println("SpNegoContext: " +
                                "negotiated mechanism = " + mechSet[j]);
                    }
                    return (mechSet[j]);
                }
            }
        }
        return null;
    }

    public final boolean isEstablished() {
        return (state == STATE_DONE);
    }

    public final boolean isMechContextEstablished() {
        if (mechContext != null) {
            return mechContext.isEstablished();
        } else {
            if (DEBUG) {
                System.out.println("The underlying mechansim context has " +
                                        "not been initialized");
            }
            return false;
        }
    }

    public final byte [] export() throws GSSException {
        throw new GSSException(GSSException.UNAVAILABLE, -1,
                               "GSS Export Context not available");
    }

    /**
     * Sets the channel bindings to be used during context
     * establishment.
     */
    public final void setChannelBinding(ChannelBinding channelBinding)
        throws GSSException {
        this.channelBinding = channelBinding;
    }

    final ChannelBinding getChannelBinding() {
        return channelBinding;
    }

    /*
     * Anonymity is a little different in that after an application
     * requests anonymity it will want to know whether the mechanism
     * can support it or not, prior to sending any tokens across for
     * context establishment. Since this is from the initiator's
     * perspective, it essentially requests that the initiator be
     * anonymous.
     */
    public final void requestAnonymity(boolean value) throws GSSException {
        // Ignore silently. Application will check back with
        // getAnonymityState.
    }

    // RFC 2853 actually calls for this to be called after context
    // establishment to get the right answer, but that is
    // incorrect. The application may not want to send over any
    // tokens if anonymity is not available.
    public final boolean getAnonymityState() {
        return false;
    }

    /**
     * Requests the desired lifetime. Can only be used on the context
     * initiator's side.
     */
    public void requestLifetime(int lifetime) throws GSSException {
        if (state == STATE_NEW && isInitiator())
            this.lifetime = lifetime;
    }

    /**
     * The lifetime remaining for this context.
     */
    public final int getLifetime() {
        if (mechContext != null) {
            return mechContext.getLifetime();
        } else {
            return GSSContext.INDEFINITE_LIFETIME;
        }
    }

    public final boolean isTransferable() throws GSSException {
        return false;
    }

    /**
     * Requests that sequence checking be done on the GSS wrap and MIC
     * tokens.
     */
    public final void requestSequenceDet(boolean value) throws GSSException {
        if (state == STATE_NEW && isInitiator())
            sequenceDetState  = value;
    }

    /**
     * Is sequence checking enabled on the GSS Wrap and MIC tokens?
     * We enable sequence checking if replay detection is enabled.
     */
    public final boolean getSequenceDetState() {
        return sequenceDetState || replayDetState;
    }

    /**
     * Requests that replay detection be done on the GSS wrap and MIC
     * tokens.
     */
    public final void requestReplayDet(boolean value) throws GSSException {
        if (state == STATE_NEW && isInitiator())
            replayDetState  = value;
    }

    /**
     * Is replay detection enabled on the GSS wrap and MIC tokens?
     * We enable replay detection if sequence checking is enabled.
     */
    public final boolean getReplayDetState() {
        return replayDetState || sequenceDetState;
    }

    public final GSSNameSpi getTargName() throws GSSException {
        // fill-in the GSSName
        // get the peer name for the mechanism
        if (mechContext != null) {
            GSSNameImpl targName = (GSSNameImpl)mechContext.getTargName();
            peerName = (GSSNameSpi) targName.getElement(internal_mech);
            return peerName;
        } else {
            if (DEBUG) {
                System.out.println("The underlying mechansim context has " +
                                        "not been initialized");
            }
            return null;
        }
    }

    public final GSSNameSpi getSrcName() throws GSSException {
        // fill-in the GSSName
        // get the src name for the mechanism
        if (mechContext != null) {
            GSSNameImpl srcName = (GSSNameImpl)mechContext.getSrcName();
            myName = (GSSNameSpi) srcName.getElement(internal_mech);
            return myName;
        } else {
            if (DEBUG) {
                System.out.println("The underlying mechansim context has " +
                                        "not been initialized");
            }
            return null;
        }
    }

    /**
     * Returns the delegated credential for the context. This
     * is an optional feature of contexts which not all
     * mechanisms will support. A context can be requested to
     * support credential delegation by using the <b>CRED_DELEG</b>.
     * This is only valid on the acceptor side of the context.
     * @return GSSCredentialSpi object for the delegated credential
     * @exception GSSException
     * @see GSSContext#getDelegCredState
     */
    public final GSSCredentialSpi getDelegCred() throws GSSException {
        if (state != STATE_IN_PROCESS && state != STATE_DONE)
            throw new GSSException(GSSException.NO_CONTEXT);
        if (mechContext != null) {
            GSSCredentialImpl delegCred =
                        (GSSCredentialImpl)mechContext.getDelegCred();
            // determine delegated cred element usage
            boolean initiate = false;
            if (delegCred.getUsage() == GSSCredential.INITIATE_ONLY) {
                initiate = true;
            }
            GSSCredentialSpi mechCred = (GSSCredentialSpi)
                                delegCred.getElement(internal_mech, initiate);
            SpNegoCredElement cred = new SpNegoCredElement(mechCred);
            return cred.getInternalCred();
        } else {
            throw new GSSException(GSSException.NO_CONTEXT, -1,
                                "getDelegCred called in invalid state!");
        }
    }

    public final int getWrapSizeLimit(int qop, boolean confReq,
                                       int maxTokSize) throws GSSException {
        if (mechContext != null) {
            return mechContext.getWrapSizeLimit(qop, confReq, maxTokSize);
        } else {
            throw new GSSException(GSSException.NO_CONTEXT, -1,
                                "getWrapSizeLimit called in invalid state!");
        }
    }

    public final byte[] wrap(byte inBuf[], int offset, int len,
                             MessageProp msgProp) throws GSSException {
        if (mechContext != null) {
            return mechContext.wrap(inBuf, offset, len, msgProp);
        } else {
            throw new GSSException(GSSException.NO_CONTEXT, -1,
                                "Wrap called in invalid state!");
        }
    }

    public final void wrap(InputStream is, OutputStream os,
                            MessageProp msgProp) throws GSSException {
        if (mechContext != null) {
            mechContext.wrap(is, os, msgProp);
        } else {
            throw new GSSException(GSSException.NO_CONTEXT, -1,
                                "Wrap called in invalid state!");
        }
    }

    public final byte[] unwrap(byte inBuf[], int offset, int len,
                               MessageProp msgProp)
        throws GSSException {
        if (mechContext != null) {
            return mechContext.unwrap(inBuf, offset, len, msgProp);
        } else {
            throw new GSSException(GSSException.NO_CONTEXT, -1,
                                "UnWrap called in invalid state!");
        }
    }

    public final void unwrap(InputStream is, OutputStream os,
                             MessageProp msgProp) throws GSSException {
        if (mechContext != null) {
            mechContext.unwrap(is, os, msgProp);
        } else {
            throw new GSSException(GSSException.NO_CONTEXT, -1,
                                "UnWrap called in invalid state!");
        }
    }

    public final byte[] getMIC(byte []inMsg, int offset, int len,
                               MessageProp msgProp)
        throws GSSException {
        if (mechContext != null) {
            return mechContext.getMIC(inMsg, offset, len, msgProp);
        } else {
            throw new GSSException(GSSException.NO_CONTEXT, -1,
                                "getMIC called in invalid state!");
        }
    }

    public final void getMIC(InputStream is, OutputStream os,
                              MessageProp msgProp) throws GSSException {
        if (mechContext != null) {
            mechContext.getMIC(is, os, msgProp);
        } else {
            throw new GSSException(GSSException.NO_CONTEXT, -1,
                                "getMIC called in invalid state!");
        }
    }

    public final void verifyMIC(byte []inTok, int tokOffset, int tokLen,
                                byte[] inMsg, int msgOffset, int msgLen,
                                MessageProp msgProp)
        throws GSSException {
        if (mechContext != null) {
            mechContext.verifyMIC(inTok, tokOffset, tokLen, inMsg, msgOffset,
                                msgLen,  msgProp);
        } else {
            throw new GSSException(GSSException.NO_CONTEXT, -1,
                                "verifyMIC called in invalid state!");
        }
    }

    public final void verifyMIC(InputStream is, InputStream msgStr,
                                 MessageProp msgProp) throws GSSException {
        if (mechContext != null) {
            mechContext.verifyMIC(is, msgStr, msgProp);
        } else {
            throw new GSSException(GSSException.NO_CONTEXT, -1,
                                "verifyMIC called in invalid state!");
        }
    }

    private static String printState(int state) {
        switch (state) {
          case STATE_NEW:
                return ("STATE_NEW");
          case STATE_IN_PROCESS:
                return ("STATE_IN_PROCESS");
          case STATE_DONE:
                return ("STATE_DONE");
          case STATE_DELETED:
                return ("STATE_DELETED");
          default:
                return ("Unknown state " + state);
        }
    }
}
