package org.jacorb.security.sas;

/*
 *        JacORB - a free Java ORB
 *
 *   Copyright (C) 2002-2004 Gerald Brose
 *
 *   This library is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU Library General Public
 *   License as published by the Free Software Foundation; either
 *   version 2 of the License, or (at your option) 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
 *   Library General Public License for more details.
 *
 *   You should have received a copy of the GNU Library General Public
 *   License along with this library; if not, write to the Free
 *   Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

import java.security.Provider;

import org.ietf.jgss.GSSException;
import org.ietf.jgss.Oid;
import org.omg.CORBA.Any;
import org.omg.CORBA.ORB;
import org.omg.GSSUP.InitialContextToken;
import org.omg.GSSUP.InitialContextTokenHelper;
import org.omg.IOP.Codec;

import sun.security.jgss.spi.GSSNameSpi;

/**
 * This is the GSS-API Sercurity Provider Interface (SPI) for the GSSUP Name
 *
 * @author David Robison
 * @version $Id: GSSUPNameSpi.java,v 1.14 2004-05-07 13:08:29 david.robison Exp $
 */

public final class GSSUPNameSpi 
    implements GSSNameSpi
{
    private static Oid mechOid;

    private Provider provider;
    private Oid nameTypeOid;

    private InitialContextToken subject = null;

    static
    {
        try
        {
            mechOid = new Oid("2.23.130.1.1.1");
        }
        catch (GSSException e)
        {
        }
    }

    public GSSUPNameSpi(Provider provider, Oid mechOid, byte[] name ,Oid nameTypeOid)
    {
        this.provider = provider;
        this.nameTypeOid = nameTypeOid;
        //GSSUPNameSpi.mechOid = mechOid;

        // parse the name
        /*
        if (name.length > 0)
        {
            try
            {
                Any any = 
                    ((GSSUPProvider)provider).getCodec().decode_value( name, 
                                                      InitialContextTokenHelper.type());
                subject = InitialContextTokenHelper.extract(any);
            }
            catch (Exception e)
            {
                // logger.error("Error creating GSSNameSpi: " + e);
                subject = new InitialContextToken(new byte[0], new byte[0], new byte[0]);
            }
        }
        else
        {
            subject = new InitialContextToken(new byte[0], new byte[0], new byte[0]);
        }
        */
    }

    public static byte[] encode(ORB orb, Codec codec, String username, String password, byte[] target_name)
    {
        InitialContextToken subject = null;
        try
        {
            subject = new InitialContextToken( username.getBytes("UTF-8"),
                                               password.getBytes("UTF-8"),
                                               target_name);
        }
        catch(java.io.UnsupportedEncodingException e)
        {
            //should never happen
            // logger.error("Error creating InitialContextToken: " + e);
            return new byte[0];
        }
        byte[] out = null;
        Any any = orb.create_any();
        InitialContextTokenHelper.insert( any, subject );
        try
        {
            out = codec.encode_value( any );
        }
        catch (Exception e)
        {
            // logger.error("Error encoding for GSSNameSpi: " + e);
            return new byte[0];
        }

        byte[] mechOidArray = null;
        try
        {
            mechOidArray = mechOid.getDER();
        }
        catch(org.ietf.jgss.GSSException e)
        {
            // logger.error("Error retrieving mechOid DER: " + e);
            return new byte[0];
        }

        int length = out.length + mechOidArray.length;
        byte[] encodedLength = null;

        if((length >> 7) == 0)
        {
            //length fits into 7 bit
            encodedLength = new byte[]{(byte) 0x60,
                                       (byte) length};
        }
        else if((length >> 14) == 0)
        {
            //length fits into 14 bit
            encodedLength = new byte[]{(byte) 0x60,
                                       (byte) ((length >> 7) | 0x80),
                                       (byte)  (length & 0x7F)};
        }
        else if((length >> 21) == 0)
        {
            //length fits into 21 bit
            encodedLength = new byte[]{(byte) 0x60,
                                       (byte)  ((length >> 14)         | 0x80),
                                       (byte) (((length >>  7) & 0x7F) | 0x80),
                                       (byte)   (length        & 0x7F)};
        }
        else if((length >> 28) == 0)
        {
            //length fits into 28 bit
            encodedLength = new byte[]{(byte) 0x60,
                                       (byte)  ((length >> 21)         | 0x80),
                                       (byte) (((length >> 14) & 0x7F) | 0x80),
                                       (byte) (((length >>  7) & 0x7F) | 0x80),
                                       (byte)  (length         & 0x7F)};
        }
        else
        {
            //length fits into 32 bit
            encodedLength = new byte[]{(byte) 0x60,
                                       (byte)  ((length >> 28)         | 0x80),
                                       (byte) (((length >> 21) & 0x7F) | 0x80),
                                       (byte) (((length >> 14) & 0x7F) | 0x80),
                                       (byte) (((length >>  7) & 0x7F) | 0x80),
                                       (byte)   (length        & 0x7F)};
        }

        byte[] completeContext = new byte[length + encodedLength.length];
        System.arraycopy(encodedLength, 0,
                         completeContext, 0,
                         encodedLength.length);
        System.arraycopy(mechOidArray, 0,
                         completeContext, encodedLength.length,
                         mechOidArray.length);
        System.arraycopy(out, 0,
                         completeContext, encodedLength.length + mechOidArray.length,
                         out.length);

        return completeContext;
    }

    public static byte[] encode(ORB orb, Codec codec, String username, char[] password, String target_name)
    {
        return encode(orb, codec, username, new String(password), target_name.getBytes());
    }

    public static InitialContextToken decode(ORB orb, Codec codec, byte[] gssToken)
    {
        if(gssToken[0] != 0x60)
        {
            // logger.error("GSSToken doesn't start with expected value '0x60'");
            return null;
        }

        //skip total size, the GSSToken already has the correct length

        //find first octet where the MSB is zero
        int index = 1;
        while(index < gssToken.length &&
              (gssToken[index] & 0x80) == 1)
        {
            ++index;
        }

        if(index == gssToken.length)
        {
            //end not found
            // logger.error("GSSToken doesn't contain valid length");
            return null;
        }

        byte[] mechOidArray = null;
        try
        {
            mechOidArray = mechOid.getDER();
        }
        catch(org.ietf.jgss.GSSException e)
        {
            // logger.error("Error retrieving mechOid DER: " + e);
            return null;
        }

        //skip last octet of length
        ++index;

        if((index + mechOidArray.length) >= gssToken.length)
        {
            // logger.error("GSSToken doesn't contain OID");
            return null;
        }

        for(int i = 0; i < mechOidArray.length; ++i)
        {
            if(mechOidArray[i] != gssToken[index + i])
            {
                // logger.error("GSSToken doesn't contain GSSUPMechOID");
                return null;
            }
        }

        //skip oid
        index += mechOidArray.length;

        byte[] icToken = new byte[gssToken.length - index];
        System.arraycopy(gssToken, index, icToken, 0, icToken.length);

        try
        {
            Any any =
            codec.decode_value(
                icToken,
                InitialContextTokenHelper.type());
            return InitialContextTokenHelper.extract(any);
        }
        catch (Exception e)
        {
            // logger.error("Error decoding for GSSNameSpi: " + e);
        }
        //logger.error("Bailout - GSSUP");
        return null;
    }

    public Provider getProvider()
    {
        return provider;
    }

    public boolean equals(GSSNameSpi name) throws GSSException
    {
        return subject.equals(((GSSUPNameSpi)name).subject);
    }

    public byte[] export() throws GSSException
    {
        throw new GSSException(GSSException.FAILURE, GSSException.FAILURE, "Not Implemented");
        /*
        //System.out.println("GSSUPNameSpi.export");
        Any any = ((GSSUPProvider)provider).getORB().create_any();
        InitialContextTokenHelper.insert( any, subject );
        byte[] out = new byte[0];
        try
        {
            out = ((GSSUPProvider)provider).getCodec().encode_value( any );
        }
        catch (Exception e)
        {
            // logger.error("Error encoding for GSSNameSpi: " + e);
        }
        return out;
        */
    }

    public Oid getMechanism()
    {
        return mechOid;
    }

    public String toString()
    {
        return null;
        /*
        Any any = ((GSSUPProvider)provider).getORB().create_any();
        InitialContextTokenHelper.insert( any, subject );
        byte[] out = new byte[0];
        try
        {
            out = ((GSSUPProvider)provider).getCodec().encode_value( any );
        }
        catch (Exception e)
        {
            // logger.error("Error encoding for GSSNameSpi: " + e);
        }
        return new String(out);
        */
    }

    public Oid getStringNameType()
    {
        return nameTypeOid;
    }

    public boolean isAnonymousName()
    {
        System.out.println("GSSUPNameSpi.isAnonymousName");
        return false;
    }
}
