/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2004 Bull S.A.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id: Csiv2IorInterceptor.java 15428 2008-10-07 11:20:29Z sauthieg $
 * --------------------------------------------------------------------------
 */
package org.ow2.jonas.security.iiop;

import java.rmi.Remote;

import javax.rmi.CORBA.Tie;

import org.jacorb.orb.iiop.IIOPAddress;
import org.jacorb.orb.iiop.IIOPProfile;
import org.jacorb.poa.RequestProcessor;
import org.omg.CORBA.Any;
import org.omg.CORBA.INV_POLICY;
import org.omg.CSIIOP.AS_ContextSec;
import org.omg.CSIIOP.CompoundSecMech;
import org.omg.CSIIOP.CompoundSecMechList;
import org.omg.CSIIOP.CompoundSecMechListHelper;
import org.omg.CSIIOP.DetectMisordering;
import org.omg.CSIIOP.DetectReplay;
import org.omg.CSIIOP.Integrity;
import org.omg.CSIIOP.SAS_ContextSec;
import org.omg.CSIIOP.ServiceConfiguration;
import org.omg.CSIIOP.TAG_CSI_SEC_MECH_LIST;
import org.omg.CSIIOP.TAG_NULL_TAG;
import org.omg.CSIIOP.TAG_TLS_SEC_TRANS;
import org.omg.CSIIOP.TLS_SEC_TRANS;
import org.omg.CSIIOP.TLS_SEC_TRANSHelper;
import org.omg.IOP.Codec;
import org.omg.IOP.TAG_INTERNET_IOP;
import org.omg.IOP.TaggedComponent;
import org.omg.IOP.CodecPackage.InvalidTypeForEncoding;
import org.omg.PortableInterceptor.IORInfo;
import org.omg.PortableServer.Servant;
import org.omg.SSLIOP.SSL;
import org.omg.SSLIOP.SSLHelper;
import org.omg.SSLIOP.TAG_SSL_SEC_TRANS;
import org.ow2.jonas.deployment.ejb.BeanDesc;
import org.ow2.jonas.lib.ejb21.JHome;
import org.ow2.jonas.lib.ejb21.JRemote;

import org.ow2.carol.util.csiv2.SasComponent;
import org.ow2.carol.util.csiv2.SasPolicy;
import org.ow2.carol.util.csiv2.struct.AsStruct;
import org.ow2.carol.util.csiv2.struct.SasStruct;
import org.ow2.carol.util.csiv2.struct.TransportStruct;


import org.objectweb.util.monolog.api.BasicLevel;
import org.objectweb.util.monolog.api.Logger;

/**
 * IOR interceptor for the supports of CSiv2 level 0 as described by chapter
 * 19.8.2.5 Stateful support is not require for level 0
 * @author Florent Benoit
 * @see Common Secure Interoperability V2 Specification (July 23,2001)
 */
public class Csiv2IorInterceptor extends org.omg.CORBA.LocalObject implements
        org.omg.PortableInterceptor.IORInterceptor {

    /**
     * Name
     */
    private static final String NAME = "Csiv2IorInterceptor";

    /**
     * Codec to use (when encoding Any object)
     */
    private Codec codec = null;

    /**
     * Logger to use
     */
    private Logger logger = null;

    /**
     * Logger details (On catching exception)
     */
    private Logger loggerDetails = null;

    /**
     * Constructor
     * @param codec used for encoding any objects
     * @param logger used for logging useful information
     * @param loggerDetails for all information (useless for most time :)
     */
    public Csiv2IorInterceptor(Codec codec, Logger logger, Logger loggerDetails) {
        this.codec = codec;
        this.logger = logger;
        this.loggerDetails = loggerDetails;
    }

    /**
     * Check if there is a CSI v2 policy and there is one add the csiv2
     * component into the IOR. The tagged Component is built from information of
     * csiv2 policy object
     * @see org.omg.PortableInterceptor.IORInterceptorOperations#establish_components(org.omg.PortableInterceptor.IORInfo)
     */
    public void establish_components(IORInfo info) {
        SasPolicy sasPolicy = null;
        SasComponent sasComponent = null;

        try {
            sasPolicy = (SasPolicy) info.get_effective_policy(SasPolicy.POLICY_TYPE);

            // build CSiv2 tagged component as required by spec
            // 16.3.1 compound mechanisms [62] and 16.5.1 [132]
            TaggedComponent taggedComponent = null;

            if (sasPolicy != null) {
                sasComponent = sasPolicy.getSasComponent();
            } else {
                // It may be a remote interface
                Thread currentThread = Thread.currentThread();
                if (!(currentThread instanceof RequestProcessor)) {
                    return;
                }
                RequestProcessor rp = (RequestProcessor) currentThread;
                Servant servant = rp.getServant();
                if (servant == null) {
                    return;
                }
                Tie tie = null;
                if (!(servant instanceof Tie)) {
                    return;
                }
                tie = (Tie) servant;
                Remote target = tie.getTarget();
                if (target == null) {
                    return;
                }

                if (!(target instanceof JHome) && !(target instanceof JRemote)) {
                    return;
                }
                BeanDesc bd = null;
                if (target instanceof JHome) {
                    bd = ((JHome) target).getDd();
                } else if (target instanceof JRemote) {
                    bd =  ((JRemote) target).getBf().getDeploymentDescriptor();
                }
                sasComponent = bd.getSasComponent();
            }

            if (sasComponent == null) {
                if (loggerDetails.isLoggable(BasicLevel.DEBUG)) {
                    loggerDetails.log(BasicLevel.DEBUG, "No Sas component was found, will not write any infos into IOR.");
                }
                return;
            }

            try {

                taggedComponent = buildCSIv2Component(sasComponent);

                // And add it into the ior
                info.add_ior_component_to_profile(taggedComponent, TAG_INTERNET_IOP.value);

            } catch (Csiv2InterceptorException cie) {
                logger.log(BasicLevel.ERROR, "Cannot build Csiv2 component, cannot add it. Component = "
                        + taggedComponent);
            }

        } catch (INV_POLICY e) {
            if (logger.isLoggable(BasicLevel.DEBUG)) {
                logger.log(BasicLevel.DEBUG, "No policy found");
            }

        } finally {

            // Add SSL
            if (sasComponent != null) {
                // Need to patch profile port if SSL (port should be 0)
                TransportStruct transportStruct = sasComponent.getTransport();
                if (transportStruct.getTargetRequires() > 0) {
                    org.omg.ETF.Profile profile = ((org.jacorb.orb.portableInterceptor.IORInfoImpl) info).get_profile(0);
                    if (profile instanceof IIOPProfile) {
                        if (logger.isLoggable(BasicLevel.DEBUG)) {
                            logger.log(BasicLevel.DEBUG, "Set port to 0");
                        }

                        IIOPProfile iiopp = (IIOPProfile) profile;
                        IIOPAddress iiopa = (IIOPAddress) iiopp.getAddress();
                        iiopa.setPort(0);
                        ((IIOPProfile) profile).patchPrimaryAddress(iiopa);

                    }
                }
            }

            try {
                info.add_ior_component_to_profile(buildSslTaggedComponent(sasComponent), TAG_INTERNET_IOP.value);
            } catch (Csiv2InterceptorException cie) {
                logger.log(BasicLevel.ERROR, "Cannot add SSL tagged component" + cie.getMessage(), cie);
            }


        }


    }

    /**
     * Provides an opportunity to destroy this interceptor.
     */
    public void destroy() {
    }

    /**
     * Returns the name of the interceptor.
     * @return the name of the interceptor.
     */
    public String name() {
        return NAME;
    }

    /**
     * CSIv2 component is built from info within sasComponent which was
     * contained in the policy object during the bind of the object <br>
     * 16.4.2 Transport Mechanism Configuration <br>
     * [113] The configuration of transport-layer security mechanisms is
     * specified in IORs. Support for CSI is indicated within an IOR profile by
     * the presence of at most one TAG_CSI_SEC_MECH_LIST tagged component that
     * defines the mechanism configuration pertaining to the profile. This
     * component contains a list of one or more CompoundSecMech structures, each
     * of which defines the layer-specific security mechanisms that comprise a
     * compound mechanism that is supported by the target. This specification
     * does not define support for CSI mechanisms in multiple-component IOR
     * profiles. <br>
     * [114] Each CompoundSecMech structure contains a transport_mech field that
     * defines the transport-layer security mechanism of the compound mechanism.
     * A compound mechanism that does not implement security functionality at
     * the transport layer shall contain the TAG_NULL_TAG component in its
     * transport_mech field. Otherwise, the transport_mech field shall contain a
     * tagged component that defines a transport protocol and its configuration.
     * Section , TAG_SSL_SEC_TRANS, on page 16-33 and Section ,
     * TAG_SECIOP_SEC_TRANS, on page 16-34 define valid transportlayer
     * components that can be used in the transport_mech field.
     * @param sasComponent information to build component
     * @return CSIv2 component to be put in IOR
     * @throws Csiv2InterceptorException if there is a failure
     */
    private TaggedComponent buildCSIv2Component(SasComponent sasComponent) throws Csiv2InterceptorException {
        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "");
        }

        // @see 16.5.1 [132] SecMechList
        // build list of compound_sec_mech
        CompoundSecMech[] mechanismList = buildCompoundSecMechs(sasComponent);
        CompoundSecMechList compoundSecMechList = new CompoundSecMechList(Csiv2Const.STATEFUL_MODE, mechanismList);


        // use Any object
        Any pAny = ORBHelper.getOrb().create_any();
        CompoundSecMechListHelper.insert(pAny, compoundSecMechList);
        byte[] componentData = null;
        try {
            componentData = codec.encode_value(pAny);
        } catch (InvalidTypeForEncoding itfe) {
            throw new Csiv2InterceptorException("Cannot encode a given any corba object", itfe);
        }

        // Create tagged component TAG_CSI_SEC_MECH_LIST
        TaggedComponent taggedComponent = new TaggedComponent(TAG_CSI_SEC_MECH_LIST.value, componentData);

        return taggedComponent;
    }

    /**
     * Build all CompoundSecMech objects for building TAG_CSI_SEC_MECH_LIST
     * CompoundSecMech is composed of (see [132] 16.5.1 p16-35)
     * <ul>
     * <li>target_requires</li>
     * <li>transport_mech</li>
     * <li>as_context_mech</li>
     * <li>sas_context_mech</li>
     * </ul>
     * @param sasComponent information to build component
     * @return CompoundSecMech objects for building TAG_CSI_SEC_MECH_LIST
     * @throws Csiv2InterceptorException if there is a failure
     */
    private CompoundSecMech[] buildCompoundSecMechs(SasComponent sasComponent) throws Csiv2InterceptorException {
        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "");
        }

        // build transport_mech
        TaggedComponent transportMech = buildTransportMech(sasComponent);

        // build as_context_mech
        AS_ContextSec asContextMech = buildAsContextMech(sasComponent);

        // build sas_context_mech
        SAS_ContextSec sasContextMech = buildSasContextMech(sasComponent);

        // See [135]
        // The target_requires field of the CompoundSecMech structure is used to
        // designate a required outcome that shall be satisfied by one or more
        // supporting (but not requiring) layers. The target_requires field also
        // represents all the options required independently by the various
        // layers as defined within the mechanism.
        short targetRequires = (short) (sasComponent.getTransport().getTargetRequires() | asContextMech.target_requires | sasContextMech.target_requires);

        // Only one compound sec mech
        CompoundSecMech[] compoundSecMechs = new CompoundSecMech[1];
        compoundSecMechs[0] = new CompoundSecMech(targetRequires, transportMech, asContextMech, sasContextMech);

        // return objects
        return compoundSecMechs;
    }

    /**
     * Build the transport mech object of the Sas component
     * @param sasComponent information to build component
     * @return transport mech object
     * @throws Csiv2InterceptorException if there is a failure
     */
    private TaggedComponent buildTransportMech(SasComponent sasComponent) throws Csiv2InterceptorException {

        TaggedComponent taggedComponent = null;

        TransportStruct transportStruct = sasComponent.getTransport();

        if (transportStruct.getTargetSupports() == 0 && transportStruct.getTargetRequires() == 0) {
            // TAG_NULL_TAG [150] This new tagged component is used in the
            // transport_mech field of a CompoundSecMech structure to indicate that
            // the compound mechanism does not implement security functionality at
            // the transport layer.

            return new TaggedComponent(TAG_NULL_TAG.value, Csiv2Const.EMPTY_BYTES);
        }

        /**
         * Tagged component for configuring TLS/SSL as a CSIv2 transport
         * mechanism is the constant : IOP::ComponentId TAG_TLS_SEC_TRANS = 36;
         */
        TLS_SEC_TRANS tlsSecTrans = new TLS_SEC_TRANS(transportStruct.getTargetSupports(), transportStruct.getTargetRequires(), transportStruct.getTransportAddress());

        // use Any object
        Any pAny = ORBHelper.getOrb().create_any();
        TLS_SEC_TRANSHelper.insert(pAny, tlsSecTrans);
        byte[] componentData = null;
        try {
            componentData = codec.encode_value(pAny);
        } catch (InvalidTypeForEncoding itfe) {
            throw new Csiv2InterceptorException("Cannot encode a given any corba object", itfe);
        }

        // Create tagged component TAG_CSI_SEC_MECH_LIST
        taggedComponent = new TaggedComponent(TAG_TLS_SEC_TRANS.value, componentData);

        return taggedComponent;
    }

    /**
     * Build SSL tagged component
     * @param sasComponent information for build component
     * @return SSL tagged component
     * @throws Csiv2InterceptorException if the SSL tagged cannot be built
     */
    private TaggedComponent buildSslTaggedComponent(SasComponent sasComponent) throws Csiv2InterceptorException {

        SSL ssl = null;
        int minSSlOptions = Integrity.value | DetectReplay.value | DetectMisordering.value;
        if (sasComponent != null) {
            TransportStruct transportStruct = sasComponent.getTransport();

            ssl = new SSL(transportStruct.getTargetSupports(), transportStruct.getTargetRequires(), (short) TransportStruct.getSslPort());
        } else {
            ssl = new SSL((short) minSSlOptions, (short) 0, (short) TransportStruct.getSslPort());
        }

        // use Any object
        Any pAny = ORBHelper.getOrb().create_any();
        SSLHelper.insert(pAny, ssl);
        byte[] componentData = null;
        try {
            componentData = codec.encode_value(pAny);
        } catch (InvalidTypeForEncoding itfe) {
            throw new Csiv2InterceptorException("Cannot encode a given any corba object", itfe);
        }
        return new TaggedComponent(TAG_SSL_SEC_TRANS.value, componentData);

    }




    /**
     * Build the Authentication service object of the Sas component
     * @param sasComponent information to build component
     * @return Authentication service object
     */
    private AS_ContextSec buildAsContextMech(SasComponent sasComponent) {

        AsStruct asStruct = sasComponent.getAs();

        // The asStruct object could be for authenticated or no authenticated
        AS_ContextSec asContextMech = new AS_ContextSec(asStruct.getTargetSupports(), asStruct
                .getTargetRequires(), asStruct.getClientAuthenticationMech(), asStruct.getTargetName());

        return asContextMech;

    }

    /**
     * Build the Secure Authentication service object of the Sas component
     * @param sasComponent information to build component
     * @return Secure Authentication service
     */
    private SAS_ContextSec buildSasContextMech(SasComponent sasComponent) {

        SasStruct sasStruct = sasComponent.getSas();

        /**
         * The privilege_authorities field contains a list of the names of 0 or
         * more privilege authorities in decreasing order of target preference.
         * A non-empty list indicates that the target supports authorization
         * tokens provided by a CSS (in EstablishContext messages) and acquired
         * from a named privilege authority. For us, always empty.
         */
        ServiceConfiguration[] privilegeAuthorities = new ServiceConfiguration[0];

        byte[][] supportedNamingMechanisms = sasStruct.getSupportedNamingMechanisms();
        if (logger.isLoggable(BasicLevel.DEBUG)) {
            logger.log(BasicLevel.DEBUG, "supported mechanisms = " + supportedNamingMechanisms + " and identity = "
                    + sasStruct.getSupportedIdentityTypes());
            logger.log(BasicLevel.DEBUG, "supported mechanisms size= " + supportedNamingMechanisms.length);
            logger.log(BasicLevel.DEBUG, "target supports= " + sasStruct.getTargetSupports());
        }
        /**
         * A target that supports identity assertion shall include in its IORs
         * the complete list of GSS mechanisms for which it supports identity
         * assertions using an identity token of type ITTPrincipalName. A TSS
         * shall reject requests that carry identity tokens of type
         * ITTPrincipalName constructed using a GSS mechanism that is not among
         * the GSS mechanisms supported by the target. The definition of a
         * target s list of supported GSS naming mechanisms is defined in
         * Section , struct SAS_ContextSec, on page 16-37.
         */
        // should be ITTPrincipalName if supported
        int supportedIdentityTypes = sasStruct.getSupportedIdentityTypes();

        // build sas context with info of the given object.
        SAS_ContextSec sasContextMech = new SAS_ContextSec(sasStruct.getTargetSupports(),
                sasStruct.getTargetRequires(), privilegeAuthorities, supportedNamingMechanisms, supportedIdentityTypes);

        return sasContextMech;
    }



}