/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2006 Bull S.A.S.
 * 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$
 * --------------------------------------------------------------------------
 *
 */
package org.ow2.jonas.security.interceptors.jrmp.ctxcheck;

import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.util.Arrays;

import org.ow2.carol.rmi.interceptor.api.JServerRequestInfo;
import org.ow2.carol.rmi.interceptor.spi.JServerRequestInterceptor;
import org.ow2.jonas.lib.security.context.SecurityContext;
import org.ow2.jonas.lib.security.context.SecurityCurrent;

/**
 * Checks that the security context that has been unserialized on the server
 * side has a valid signature (Nobody has changed its value).
 * @author Florent Benoit
 */
public class ServerInterceptor implements JServerRequestInterceptor {

    /**
     * JProp fully qualified Classname
     */
    private static final String JPROP_CLASSNAME = "org.ow2.jonas.lib.bootstrap.JProp";

    /**
     * Anonymous security context.
     */
    private static final SecurityContext ANON_CTX = new SecurityContext();

    /**
     * Security context with an altered identity : like Anonymous.
     */
    private static SecurityContext alteredCtx = null;

    /**
     * Interceptor name.
     */
    private static final String NAME = "CHECK_CTX_INTERCEPTOR";

    /**
     * Configuration used to check the signature.
     */
    private static CtxCheckConfig config = null;

    /**
     * Default constructor.
     */
    public ServerInterceptor() {
        // If server side
        if (config == null) {
            boolean hasJonasBase = System.getProperty("jonas.base") != null;
            boolean hasJonasRoot = System.getProperty("jonas.root") != null;
            boolean server = true;
            try {
                Thread.currentThread().getContextClassLoader().loadClass(JPROP_CLASSNAME);
            } catch (ClassNotFoundException cnfe) {
                server = false;
            }
            if (server && hasJonasBase && hasJonasRoot) {
                config = new CtxCheckConfig();
            }
        }

        if (alteredCtx == null) {
            alteredCtx = new SecurityContext("JOnAS_ALTERED_IDENTITY");
        }
    }

    /**
     * Receive request
     * @param JServerRequestInfo the jrmp server request information
     * @exception IOException if an exception occurs with the ObjectOutput
     */
    public void receiveRequest(final JServerRequestInfo jri) throws IOException {
        // Gets SecurityCurrent object (always existing in JOnAS Server)
        SecurityCurrent current = SecurityCurrent.getCurrent();
        if (current != null) {
            // Get Security context
            SecurityContext sctx = current.getSecurityContext();

            // There is a context ?
            if (sctx != null) {
                // Anonymous context ?
                if (ANON_CTX.getPrincipalName().equals(sctx.getPrincipalName()) && Arrays.equals(ANON_CTX.getRoles(), sctx.getRoles())) {
                    return;
                }

                // Not the anonymous context, needs to validate

                // No signature = error ! change the security context and raise an error
                if (sctx.getSignature() == null) {
                    current.setSecurityContext(alteredCtx);
                    throw new IOException("The security context '" + sctx + "' has no signature which is illegal with this configuration. Check that the SignLoginModule has been used.");
                }


                // Get public key
                PublicKey publickey = config.getPublicKey();

                // Build signature with data to validate (principal name + roles)
                Signature signature = null;
                try {
                    signature = Signature.getInstance("SHA1withDSA");
                } catch (NoSuchAlgorithmException e) {
                    current.setSecurityContext(alteredCtx);
                    throw new IOException("Error while getting the algorithm 'SHA1withDSA' :" + e.getMessage());
                }

                try {
                    signature.initVerify(publickey);
                } catch (InvalidKeyException e) {
                    current.setSecurityContext(alteredCtx);
                    throw new IOException("Cannot initialize the signature with the given public key:" + e.getMessage());
                }


                // Add principal name
                try {
                    signature.update(sctx.getPrincipalName().getBytes());
                } catch (SignatureException e) {
                    current.setSecurityContext(alteredCtx);
                    throw new IOException("Cannot add the bytes for the principal name '" + sctx.getPrincipalName() + "' :" + e.getMessage());
                }

                // Add roles
                String[] roles = sctx.getRoles();
                for (int r = 0; r < roles.length; r++) {
                    try {
                        signature.update(roles[r].getBytes());
                    } catch (SignatureException e) {
                        current.setSecurityContext(alteredCtx);
                        throw new IOException("Cannot add the bytes for the role '" + roles[r] + "' :" + e.getMessage());
                    }
                }

                // Check signature
                boolean trusted = false;
                try {
                    trusted = signature.verify(sctx.getSignature());
                } catch (SignatureException e) {
                    current.setSecurityContext(alteredCtx);
                    throw new IOException("The signature found in the security context '" + sctx + "' is invalid:" + e.getMessage());
                }

                // Invalid signature !
                if (!trusted) {
                    current.setSecurityContext(alteredCtx);
                    throw new IOException("The signature for the security context '" + sctx + "' has been altered by an unknown source.");
                }

            }

        }
    }



    /**
     * send reply with context
     * @param JServerRequestInfo the jrmp server request information
     * @exception IOException if an exception occur with the ObjectOutput
     */
    public void sendReply(final JServerRequestInfo jri) throws IOException {
        // Do nothing
    }

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

    public void sendException(final JServerRequestInfo jri) throws IOException {
    }

    public void sendOther(final JServerRequestInfo jri) throws IOException {
    }
}
