/* **************************************************************************

  $Archive: /njcl/src/com/novell/java/security/Vault.java $
  $Revision: 15 $
  $Modtime: 3/03/99 4:29p $

  Copyright (c) 1997-1998 Novell, Inc.  All Rights Reserved.

  THIS WORK IS AN UNPUBLISHED WORK AND CONTAINS CONFIDENTIAL PROPRIETARY
  AND TRADE SECRET INFORMATION OF NOVELL, INC. ACCESS  TO  THIS  WORK IS
  RESTRICTED TO (I) NOVELL, INC.  EMPLOYEES WHO HAVE A NEED TO  KNOW HOW
  TO  PERFORM  TASKS WITHIN  THE SCOPE  OF  THEIR   ASSIGNMENTS AND (II)
  ENTITIES OTHER  THAN  NOVELL, INC.  WHO  HAVE ENTERED INTO APPROPRIATE
  LICENSE   AGREEMENTS.  NO  PART  OF  THIS WORK MAY BE USED, PRACTICED,
  PERFORMED COPIED, DISTRIBUTED, REVISED, MODIFIED, TRANSLATED, ABRIDGED,
  CONDENSED, EXPANDED, COLLECTED, COMPILED, LINKED,  RECAST, TRANSFORMED
  OR ADAPTED  WITHOUT THE PRIOR WRITTEN CONSENT OF NOVELL, INC.  ANY USE
  OR EXPLOITATION  OF  THIS WORK WITHOUT AUTHORIZATION COULD SUBJECT THE
  PERPETRATOR  TO CRIMINAL AND CIVIL LIABILITY.

****************************************************************************/
package com.novell.java.security;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.IOException;

import java.lang.reflect.InvocationTargetException;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
import java.util.Vector;

import com.novell.java.util.Debug;
import com.novell.service.security.XplatIdentity;
import com.novell.service.security.XplatIdentityScope;
import com.novell.service.security.NdsServiceIdentity;

/**@internal
 *
 * Parser of the property file and repository for the
 * configuration information.
 *
 * @author  Jan Sture Nielsen
 * @version 1.0.0 97/11/01
 *
 * @see Authenticator
 */
class Vault
{
   final static boolean DEBUG = false;

   // Support for the new protocols has been added to those files and simple
   // testing shows that they work.  Further testing needs to be done,
   // however, to make them the default
   final static boolean USE_OLD_PROTOCOL = false;

   /**
    * non-internationalizable default properties
    */
	private final static String
      PROPERTY_FILE_SEPARATOR                = "file.separator",
		PROPERTY_FILENAME_DEFAULT					= "security.properties",
		PROPERTY_FILENAME_KEY						= "osa.security.file.name",
		PROPERTY_FILE_PATH_DEFAULT					= ".",
		PROPERTY_FILE_PATH_KEY						= "osa.security.file.path",
		// internal
      VAULT_IDENTITY_SCOPE                   = "VAULT_IDENTITY_SCOPE",
		// move to internationalizable...
		PROPERTY_BLANK									= "",
		PROPERTY_DEFAULT								= "default.",
		PROPERTY_IDENTITY								= "identity.",
		PROPERTY_IDENTITYSCOPE						= "identityscope.",
		PROPERTY_CHANNEL								= "channel.",
		PROPERTY_PROTOCOL								= "protocol.",
		PROPERTY_CLASS									= "class",
		PROPERTY_NAME									= "name",
		PROPERTY_SCOPE									= "scope";

	/**
	 * configuration properties
	 */
   private Properties configuration = new Properties();

   /**
    * vault identity scope
    */
	private IdentityScope configuredIdentities = new VaultIdentityScope();

	/**
	 * default constructor for the <code>Vault</code>
	 */
   Vault()
   {
      if(DEBUG)
         Debug.println("Vault()");

      AuthenticatorException exception;
      try
      {
			String filename = System.getProperty(PROPERTY_FILENAME_KEY,PROPERTY_FILENAME_DEFAULT);
         String path = System.getProperty(PROPERTY_FILE_PATH_KEY,PROPERTY_FILE_PATH_DEFAULT);

         File file = new File(path+System.getProperty(PROPERTY_FILE_SEPARATOR)+filename);
         configuration.load(new BufferedInputStream(new FileInputStream(file)));
		}
		catch (Throwable x)
		{
			// convert to java.util.PropertyResourceBundle
         if (USE_OLD_PROTOCOL)
         {
            configuration.put(
               "default.identityscope.1.class",
               "com.novell.service.session.xplat.XplatIdentity");
         }
         else
         {
            configuration.put(
               "default.identityscope.1.class",
               "com.novell.service.security.XplatIdentity");
         }
         configuration.put(
				"default.identityscope.1.name",
				"DEFAULT_NAME");
         configuration.put(
				"default.identityscope.1.channel.1.class",
				"com.novell.java.security.channels.NativeCodeChannelProxy");
         if (USE_OLD_PROTOCOL)
         {
            configuration.put(
               "default.identityscope.1.channel.1.protocol.1.class",
               "com.novell.service.session.xplat.XplatProtocolProxy");
         }
         else
         {
            configuration.put(
               "default.identityscope.1.channel.1.protocol.1.class",
               "com.novell.service.security.protocols.XplatProtocolProxy");
         }
		}

      parseIdentityScopes(PROPERTY_DEFAULT,configuredIdentities);
		parseIdentityScopes(PROPERTY_BLANK,configuredIdentities);

		parseIdentities(PROPERTY_DEFAULT,configuredIdentities);
		parseIdentities(PROPERTY_BLANK,configuredIdentities);
   }

	/**
	 * get the configured identities
	 */
	Identity[] getConfiguredIdentities()
	{
      if(DEBUG)
         Debug.println("Vault.getConfiguredIdentities()");

      return getIdentities(configuredIdentities);
	}

	/**
	 * get the identities within some scope
	 */
	Identity[] getIdentities(IdentityScope scope)
	{
      if(DEBUG)
         Debug.println("Vault.getIdentities("+scope+")");

      Vector i = new Vector();

      getIdentities(scope,i);

      Identity identities[] = new Identity[i.size()];
      i.copyInto(identities);

      return identities;
	}

	/**
	 * find the identities in a scope
	 *
	 * @param scope scope from which identities should be accumulated
	 * @param identities returned <code>Vector</code> of <code>Identities</code>
	 */
   private void getIdentities(IdentityScope scope, Vector identities)
   {
      if(DEBUG)
         Debug.println("Vault.getIdentities("+scope+","+identities+")");

      Enumeration e = scope.identities();

      while(e.hasMoreElements())
      {
         Identity i = (Identity) e.nextElement();

         if(DEBUG)
	         Debug.println("Vault.getIdentities("+scope+","+identities+") : "+i);

         identities.addElement(i);

         if(i instanceof IdentityScope)
            getIdentities((IdentityScope)i,identities);
      }
   }

	/**
	 * get the <code>IdentityScope</code> for an identity
	 *
	 * @param identity <code>Identity</code> for which the authentication scope
	 * should be found
	 *
	 * @returns AuthenticatableIdentityScope scope which defines the
	 * authentication mechanism
	 */
   private VaultIdentityScope getIdentityScope(Identity identity)
   {
      if(DEBUG)
         Debug.println("Vault.getIdentityScope("+identity+")");

		Identity	i = configuredIdentities.getIdentity(identity);

		if(i == null)
		{
			Identity ids[] = getConfiguredIdentities();
			if(ids.length == 0)
				throw new ConfigurationException(I2L.getString(XMsg.NO_DEFAULT_SCOPE_EXISTS));
			i = ids[0];
		}

		if(!(i instanceof IdentityScope))
			i = (IdentityScope)i.getScope();

		while(!(i instanceof VaultIdentityScope))
		{
			i = (VaultIdentityScope)i.getScope();
			if(i == configuredIdentities)
				throw new ConfigurationException(I2L.getString(XMsg.NO_SCOPE_EXISTS));
		}

		return (VaultIdentityScope)i;
   }

   /* Hopefully, it would be possible to incorporate this within the configuration
    */
   private static AuthenticatorProtocol   ServiceAuthenticator = 
             new com.novell.service.security.protocols.ServiceAuthProtocolProxy();
             
	/**
	 * get the protocols associated with an identity
	 *
	 * @param identity <code>Identity</code> whose protocols should be found
	 *
	 * @returns AuthenticationProtocol[] array of protocols required
	 * to authenticate identity
	 */
   AuthenticatorProtocol[] getProtocols(Identity identity)
   {
      if(DEBUG)
         Debug.println("Vault.getProtocols("+identity+")");

      /* Hopefully, it would be possible to incorporate this within the configuration
       */
       
      if ((identity instanceof NdsServiceIdentity) &&
          (System.getProperty ("os.name").toLowerCase().indexOf("netware") != -1))
          return new AuthenticatorProtocol[] {ServiceAuthenticator};
          
      AuthenticatorChannel channels[] = getChannels(identity);

      if(DEBUG)
         Debug.println("Vault.getProtocols("+identity+") from "+channels.length+" authentication channel(s)");

      Vector protos = new Vector();
      for(int i=0; i<channels.length;i++)
      {
         AuthenticatorProtocol p[] = channels[i].getProtocols();
         for(int j=0;j<p.length;j++)
            protos.addElement(p[j]);
      }

      AuthenticatorProtocol protocols[] = new AuthenticatorProtocol[protos.size()];
      protos.copyInto(protocols);

      if(DEBUG)
         Debug.println("Vault.getProtocols("+identity+") returning "+protocols.length+" protocol(s): "+protocols);

      return protocols;
   }

	/**
	 * get the channels associated with an identity
	 *
	 * @param identity <code>Identity</code> whose channels should be found
	 *
	 * @returns AuthenticationChannel[] array of channels required
	 * to authenticate identity
	 */
   AuthenticatorChannel[] getChannels(Identity identity)
   {
      if(DEBUG)
         Debug.println("Vault.getChannels("+identity+")");

      return getIdentityScope(identity).channels;
   }

	private class VaultIdentityScope extends IdentityScope
	{
		AuthenticatorChannel channels[];

      Vector identities = new Vector();

		VaultIdentityScope()
		{
			super(VAULT_IDENTITY_SCOPE);

         if(DEBUG)
            Debug.println("Vault.VaultIdentityScope()["+this+"]");
		}

      VaultIdentityScope(String name, IdentityScope scope,AuthenticatorChannel channels[])
         throws KeyManagementException
      {
         super(name,scope);

			this.channels = channels;

         if(DEBUG)
            Debug.println("Vault.VaultIdentityScope("+name+","+scope+","+channels+")["+this+"]");
      }

      public void addIdentity(Identity identity)
      {
         if(DEBUG)
            Debug.println("Vault.VaultIdentityScope.addIdentity("+identity+")");

         identities.addElement(identity);
      }

      public void removeIdentity(Identity identity)
      {
         if(DEBUG)
            Debug.println("Vault.VaultIdentityScope.removeIdentity("+identity+")");

         int index = identities.indexOf(identity);
         if(index == -1)
            return;

         identities.removeElementAt(index);
      }

      public Identity getIdentity(String name)
      {
         if(DEBUG)
            Debug.println("Vault.VaultIdentityScope.getIdentity("+name+")");

         Enumeration e = identities();
         while(e.hasMoreElements())
         {
            Identity entry = (Identity)e.nextElement();
            if(entry.getName().equals(name))
            {
               if(DEBUG)
                  Debug.println("Vault.VaultIdentityScope.getIdentity("+name+") found");

               return entry;
            }

            // if identity scope, recurse into it
            if(entry instanceof IdentityScope)
            {  Identity i = ((IdentityScope)entry).getIdentity(name);
               if(i != null)
                  return i;
            }
         }
         if(DEBUG)
            Debug.println("Vault.VaultIdentityScope.getIdentity("+name+") not found");

         return null;
      }

      public Identity getIdentity(PublicKey key)
      {
         if(DEBUG)
            Debug.println("Vault.VaultIdentityScope.getIdentity("+key+")");

         return null;
      }

      public Enumeration identities()
      {
         if(DEBUG)
            Debug.println("Vault.VaultIdentityScope.identities()");

         return identities.elements();
      }

      public int size()
      {
         return identities.size();
      }
	}

   private void parseIdentityScopes(String baseKey, IdentityScope scope)
   {
      if(DEBUG)
         Debug.println("Vault.parseIdentityScopes("+baseKey+","+scope+")");

      int i = 0;
      while(true)
      {
         i++;

         String key = baseKey+"identityscope."+i;

         String name = configuration.getProperty(key+".name");
         if(name == null)
            break;

         String className = configuration.getProperty(key+".class");

			try
			{
				AuthenticatorChannel channels[] = parseChannels(key);
				VaultIdentityScope is = new VaultIdentityScope(name,scope,channels);
				scope.addIdentity(is);
			}
			catch(java.lang.Throwable x)
			{
				throw new ConfigurationException(I2L.getString(XMsg.SCOPE_PARSING),x);
			}
      }
   }

   private void parseIdentities(String baseKey, IdentityScope scope)
   {
      if(DEBUG)
         Debug.println("Vault.parseIdentities("+scope+")");

      int i = 0;
      while(true)
      {
         i++;

         String key = baseKey+"identity."+i;

         String name = configuration.getProperty(key+".name");
         if(name == null)
            break;

         String className = configuration.getProperty(key+".class");

         String scopeName = configuration.getProperty(key+".scope");

			Identity s = scope.getIdentity(scopeName);
         if(s == null || !(s instanceof IdentityScope))
            throw new ConfigurationException(I2L.getString(XMsg.NO_ASSOCIATED_SCOPE));

         Class classArray [] = {name.getClass(),s.getClass().getSuperclass()};
         Object objectArray [] = {name,(IdentityScope)s};

         if(DEBUG)
         {
            Debug.println("Vault.parseIdentities() constructing "+className);
            Debug.println(" with "+classArray);
            for(int j=0;j<classArray.length;j++)
               Debug.println("  which is "+classArray[j]);
            Debug.println(" and " +objectArray);
            for(int j=0;j<objectArray.length;j++)
               Debug.println("  which is "+objectArray[j]);
         }

			Identity identity;

			try
			{
				identity = (Identity)Class.forName(className).
								getDeclaredConstructor(classArray).
								newInstance(objectArray);

// configure is a nop in all of the XplatIdentity subclasses
//				if(identity instanceof XplatIdentity)
//					((XplatIdentity)identity).configure(configuration,key);

				identity.getScope().addIdentity(identity);
			}
			catch(java.lang.Throwable x)
			{
				throw new ConfigurationException(I2L.getString(XMsg.IDENTITY_PARSING),x);
			}
      }
   }

   private AuthenticatorChannel[] parseChannels(String baseKey)
   {
      if(DEBUG)
         Debug.println("Vault.parseChannels("+baseKey+")");

      Vector channels = new Vector();

      int i = 0;
      while(true)
      {
         i++;

         String key = baseKey+".channel."+i;

         String className = configuration.getProperty(key+".class");

         if(className == null)
            break;

         try
         {
            AuthenticatorChannel channel = (AuthenticatorChannel)
               Class.forName(className).newInstance();

            channel.configure(configuration,key);

            channels.addElement(parseProtocol(key,channel));
         }
         catch(java.lang.Throwable x)
         {
            throw new ConfigurationException(I2L.getString(XMsg.CHANNEL_PARSING),x);
         }
      }
      AuthenticatorChannel array[] = new AuthenticatorChannel[channels.size()];
      channels.copyInto(array);

      return array;
   }

   private AuthenticatorChannel parseProtocol
      (String baseKey,AuthenticatorChannel channel)
   {
      if(DEBUG)
         Debug.println("Vault.parseProtocol("+baseKey+","+channel+")");

      Vector protocols = new Vector();

      int i = 0;
      while(true)
      {
         i++;

	      String key = baseKey+".protocol."+i;

         String className = configuration.getProperty(key+".class");
         if(className == null)
            break;

         try
         {
            AuthenticatorProtocol protocol = (AuthenticatorProtocol)
               Class.forName(className).newInstance();

            protocol.configure(configuration,key);

            protocols.addElement(protocol);
         }
         catch(java.lang.Throwable x)
         {
            throw new ConfigurationException(I2L.getString(XMsg.PROTOCOL_PARSING),x);
         }
      }
      AuthenticatorProtocol array[] = new AuthenticatorProtocol[protocols.size()];
      protocols.copyInto(array);

      channel.init(array);

      return channel;
   }
}

