/***********************************************************************
 * 
 *  Copyright (C) 2006 Novell, Inc. All Rights Reserved.
 *
 *  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; version 2.1
 *  of the License.
 *
 *  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 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, Novell, Inc.
 * 
 *  To contact Novell about this file by physical or electronic mail, 
 *  you may find current contact information at www.novell.com.
 * 
 *  Author: Juan Carlos Luciani <jluciani@novell.com>
 *  
 ***********************************************************************/

package com.novell.casa.authtoksvc;

import org.apache.log4j.Logger;

import java.util.*;
import java.io.*;

import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;

import java.net.URL;
import java.net.MalformedURLException;
import java.net.URLClassLoader;

/**
 * Authenticate Class.
 * <p>
 * This class processes authentication requests.
 * 
 */
public final class Authenticate implements RpcMethod
{
   private static final Logger   m_log = Logger.getLogger(Authenticate.class);
   private Logger m_trace = null;

   private static final String   m_mechanismSettingsFileName = "mechanism.settings";

   private final Map<String,AuthMechanism> m_authMechanismMap;

   private SvcConfig          m_svcConfig;
   private EnabledSvcsConfig  m_enabledSvcsConfig;

   /**
    * Constructor.
    */
   public Authenticate()
   {
      // Create a map to keep track of the authentication mechanisms
      m_authMechanismMap = new HashMap<String, AuthMechanism>();
   }     

   /**
    * Initialize the Rpc method.
    *
    * @param svcConfig           Service configuration object.
    * @param enabledSvcsConfig   Enabled services configuration object.
    * @param traceLogger         Logger for tracing requests.
    * @throws Exception
    */
   public final void init(SvcConfig svcConfig,
                          EnabledSvcsConfig enabledSvcsConfig,
                          Logger traceLogger) throws Exception
   {
      m_svcConfig = svcConfig;
      m_enabledSvcsConfig = enabledSvcsConfig;
      m_trace = traceLogger;

      // Now go through the configured authentication mechanisms, as we do so, instantiate
      // the mechanisms and place them in our map. Note that the mechanisms config folder
      // contains folders for each installed authentication mechanism. The name of these
      // folders usually match the name of the Authentication mechanisms.
      String svcConfigPath = svcConfig.getSetting(SvcConfig.ConfigFolderPath);
      File mechanismsConfigFolder = new File(svcConfigPath, "auth_mechanisms");
      try
      {
         String[] mechanismsConfigFolderObjs = mechanismsConfigFolder.list();
         if (mechanismsConfigFolderObjs != null)
         {
            for (int i = 0; i < mechanismsConfigFolderObjs.length; i++)
            {
               FileOutputStream fos = null;
               ObjectOutputStream oos = null;
               FileInputStream fis = null;
               ObjectInputStream ois = null;

               // Check if we are dealing with a file or a folder
               File mechanismFolder = new File(mechanismsConfigFolder, mechanismsConfigFolderObjs[i]);
               try
               {
                  if (mechanismFolder.isDirectory())
                  {
                     m_log.debug("init()- Mechanism folder " + mechanismFolder + " is directory");

                     // Try to obtain the mechanism settings
                     try
                     {
                        AuthMechConfig mechConfig = new AuthMechConfig(mechanismFolder + File.separator + m_mechanismSettingsFileName);

                        // Mechanism settings obtained, now instantiate it and place it in our map.
                        //
                        String mechClassName = mechConfig.getSetting(AuthMechConfig.ClassName);
                        if (mechClassName != null)
                        {
                           // We now know the name of the class implementing the mechanism, now lets
                           // get the relative path to the class file. Note that the path is relative
                           // to the root folder of our application.
                           String relativePath = mechConfig.getSetting(AuthMechConfig.RelativeClassPath);
                           if (relativePath != null)
                           {
                              // Create a file object to the folder containing the class file. Note that we need to
                              // ultimately instantiate objects from a class loaded by the same class loader that
                              // loads the AuthMechanism class to avoid ClassCastExceptions.
                              File mechClassPathFile = new File(svcConfig.getSetting(SvcConfig.AppRootPath) + relativePath);
                              m_log.debug("init()- Mechanism path = " + mechClassPathFile);
                              try
                              {
                                 URL methClassPathUrl = mechClassPathFile.toURL();
                                 URL[] urls = new URL[]{methClassPathUrl};

                                 // Create a class loader for the folder
                                 ClassLoader customClassLoader = new URLClassLoader(urls,
                                                                                    this.getClass().getClassLoader());

                                 // Load the mech class using our custom loader
                                 Class<?> mechClass = customClassLoader.loadClass(mechClassName);
                                 fos = new FileOutputStream(svcConfig.getSetting(SvcConfig.AppRootPath) + "tmp");
                                 oos = new ObjectOutputStream(fos);
                                 oos.writeObject(mechClass);
                                 fis = new FileInputStream(svcConfig.getSetting(SvcConfig.AppRootPath) + "tmp");
                                 ois = new ObjectInputStream(fis);
                                 mechClass = (Class<?>) ois.readObject();

                                 // Now reload the class using the class loader for our AuthMechanism class
                                 AuthMechanism mechanism = (AuthMechanism) mechClass.newInstance();
                                 mechanism.init(svcConfig, mechConfig);
                                 m_authMechanismMap.put(mechanism.getId(), mechanism);
                              }
                              catch (MalformedURLException e)
                              {
                                 m_log.warn("init()- MalformedURLException for "  + mechanismFolder + File.separator + m_mechanismSettingsFileName + " Exception=" + e.toString());
                              }
                              catch (ClassNotFoundException e)
                              {
                                 m_log.warn("init()- ClassNotFoundException for "  + mechanismFolder + File.separator + m_mechanismSettingsFileName + " Exception=" + e.toString());
                              }
                              catch (InstantiationException e)
                              {
                                 m_log.warn("init()- InstantiationException for "  + mechanismFolder + File.separator + m_mechanismSettingsFileName + " Exception=" + e.toString());
                              }
                              catch (IllegalAccessException e)
                              {
                                 m_log.warn("init()- IllegalAccessException for "  + mechanismFolder + File.separator + m_mechanismSettingsFileName + " Exception=" + e.toString());
                              }
                           }
                           else
                           {
                              // A relative path was not configured, check if instead a full path was configured.
                              String classPath = mechConfig.getSetting(AuthMechConfig.ClassPath);
                              if (classPath != null)
                              {
                                 // Create a file object to the folder containing the class file. Note that we need to
                                 // ultimately instantiate objects from a class loaded by the same class loader that
                                 // loads the AuthMechanism class to avoid ClassCastExceptions.
                                 File mechClassPathFile = new File(classPath);
                                 m_log.debug("init()- Mechanism path = " + mechClassPathFile);
                                 try
                                 {
                                    URL methClassPathUrl = mechClassPathFile.toURL();
                                    URL[] urls = new URL[]{methClassPathUrl};

                                    // Create a class loader for the folder
                                    ClassLoader customClassLoader = new URLClassLoader(urls);

                                    // Load the mech class using our custom loader
                                    Class<?> mechClass = customClassLoader.loadClass(mechClassName);
                                    fos = new FileOutputStream(svcConfig.getSetting(SvcConfig.AppRootPath) + "tmp");
                                    oos = new ObjectOutputStream(fos);
                                    oos.writeObject(mechClass);
                                    fis = new FileInputStream(svcConfig.getSetting(SvcConfig.AppRootPath) + "tmp");
                                    ois = new ObjectInputStream(fis);
                                    mechClass = (Class<?>) ois.readObject();

                                    // Now reload the class using the class loader for our AuthMechanism class
                                    AuthMechanism mechanism = (AuthMechanism) mechClass.newInstance();
                                    mechanism.init(svcConfig, mechConfig);
                                    m_authMechanismMap.put(mechanism.getId(), mechanism);
                                 }
                                 catch (MalformedURLException e)
                                 {
                                    m_log.warn("init()- MalformedURLException for "  + mechanismFolder + File.separator + m_mechanismSettingsFileName + " Exception=" + e.toString());
                                 }
                                 catch (ClassNotFoundException e)
                                 {
                                    m_log.warn("init()- ClassNotFoundException for "  + mechanismFolder + File.separator + m_mechanismSettingsFileName + " Exception=" + e.toString());
                                 }
                                 catch (InstantiationException e)
                                 {
                                    m_log.warn("init()- InstantiationException for "  + mechanismFolder + File.separator + m_mechanismSettingsFileName + " Exception=" + e.toString());
                                 }
                                 catch (IllegalAccessException e)
                                 {
                                    m_log.warn("init()- IllegalAccessException for "  + mechanismFolder + File.separator + m_mechanismSettingsFileName + " Exception=" + e.toString());
                                 }
                              }
                              else
                              {
                                 m_log.warn("init()- No configuration to find class path to load "  + mechanismFolder + File.separator + m_mechanismSettingsFileName);
                              }
                           }
                        }
                        else
                        {
                           m_log.warn("init()- No configured mechanism class name for "  + mechanismFolder + File.separator + m_mechanismSettingsFileName);
                        }
                     }
                     catch (SecurityException e)
                     {
                        m_log.warn("init()- SecurityException accessing " + mechanismFolder + File.separator + m_mechanismSettingsFileName + " Exception=" + e.toString());
                     }
                     catch (FileNotFoundException e)
                     {
                        m_log.warn("init()- No authentication policy file for " + mechanismFolder);
                     }
                     catch (IOException e)
                     {
                        m_log.warn("init()- IOException reading " + mechanismFolder + File.separator + m_mechanismSettingsFileName + " Exception=" + e.toString());
                     }
                     catch (Exception e)
                     {
                        m_log.warn("init()- Exception instantiating mechConfig or mechanism " + mechanismFolder + File.separator + m_mechanismSettingsFileName + " Exception=" + e.toString());
                     }
                  }
               }
               catch (SecurityException e)
               {
                  m_log.warn("init()- SecurityException accessing " + mechanismFolder + " Exception=" + e.toString());
               }
               finally
               {
                  if (fos != null)
                  {
                     try
                     {
                        fos.flush();
                        fos.close();
                     }
                     catch (IOException e)
                     {
                        // Do nothing
                     }
                  }
                  if (oos != null)
                  {
                     try
                     {
                        oos.flush();
                        oos.close();
                     }
                     catch (IOException e)
                     {
                        // Do nothing
                     }
                  }
                  if (fis != null)
                  {
                     try
                     {
                        fis.close();
                     }
                     catch (IOException e)
                     {
                        // Do nothing
                     }
                  }
                  if (ois != null)
                  {
                     try
                     {
                        ois.close();
                     }
                     catch (IOException e)
                     {
                        // Do nothing
                     }
                  }
               }
            }
         }
         else
         {
            m_log.warn("init()- Unable to obtain mechanisms folder " + mechanismsConfigFolder + " objects");
         }
      }
      catch (SecurityException e)
      {
         m_log.warn("init()- SecurityException accessing " + mechanismsConfigFolder + " Exception=" + e.toString());
      }
   }

   /**
    * Process Rpc.
    *
    * @param inStream   Input data stream (Request data).
    * @param out        Output data print writer (Reply data).
    */
   public final void invoke(InputStream inStream, PrintWriter out)
   {
      AuthReqMsg authReqMsg = null;

      try
      {
         m_log.debug("invoke()");

         // Parse the AuthReqMsg sent from the client
         authReqMsg = new AuthReqMsg(inStream);

         // Get the necessary authentication mechanism
         AuthMechanism authMechanism = m_authMechanismMap.get(authReqMsg.getMechanismId());
         if (authMechanism != null)
         {
            // Invoke the mechanism to authenticate the entity
            String identId = authMechanism.invoke(authReqMsg);

            // Create response based on the identity resolution results
            if (identId != null && identId.length() != 0)
            {
               m_log.info("invoke()- identId resolved, " + identId);

               // An identity was resolved, get a SessionToken for it.
               SessionToken sessionToken = new SessionToken(identId,
                                                            authReqMsg.getRealm(),
                                                            m_svcConfig.getSetting(SvcConfig.SessionTokenLifetime));

               // Write out the response
               String respLifetime = Integer.toString(Integer.valueOf(m_svcConfig.getSetting(SvcConfig.SessionTokenLifetime)).intValue()
                                                      - Integer.valueOf(m_svcConfig.getSetting(SvcConfig.LifetimeShorter)).intValue());
               AuthRespMsg authRespMsg = new AuthRespMsg(ProtoDefs.httpOkStatusMsg,
                                                         ProtoDefs.httpOkStatusCode,
                                                         sessionToken.serialize(),
                                                         respLifetime);
               out.println(authRespMsg.toString());

               m_trace.info("Authenticate Rpc, Mech=" + authReqMsg.getMechanismId()
                            + ", Realm=" + authReqMsg.getRealm()
                            + ", Status=SUCCESS");
            }
            else
            {
               m_log.info("invoke()- identId not resolved");
               m_trace.info("Authenticate Rpc, Mech=" + authReqMsg.getMechanismId()
                            + ", Realm=" + authReqMsg.getRealm()
                            + ", Status=UNSUCCESSFUL");

               // Write out the response
               AuthRespMsg authRespMsg = new AuthRespMsg(ProtoDefs.httpUnauthorizedStatusMsg,
                                                         ProtoDefs.httpUnauthorizedStatusCode);
               out.println(authRespMsg.toString());
            }
         }
         else
         {
            m_log.warn("invoke()- Unsupported mechanism " + authReqMsg.getMechanismId());
            m_trace.info("Authenticate Rpc, Mech=" + authReqMsg.getMechanismId()
                         + ", Realm=" + authReqMsg.getRealm()
                         + ", Status=UNSUPPORTED_MECHANISM");

            // Write out the response
            AuthRespMsg authRespMsg = new AuthRespMsg(ProtoDefs.httpNotFoundStatusMsg,
                                                      ProtoDefs.httpNotFoundStatusCode);
            out.println(authRespMsg.toString());
         }
      }
      catch (Exception e)
      {
         m_log.error("invoke()- Exception: " + e.toString());
         m_trace.info("Authenticate Rpc, Status=INTERNAL_ERROR");

         // Write out the response
         try
         {
            AuthRespMsg authRespMsg = new AuthRespMsg(ProtoDefs.httpServerErrorStatusMsg,
                                                      ProtoDefs.httpServerErrorStatusCode);
            out.println(authRespMsg.toString());
         }
         catch (Exception e2)
         {
            m_log.error("invoke()- Exception trying to construct response msg: " + e2.toString());
         }
      }
      finally
      {
         // Make sure to invoke the cleanup method on the AuthReqMsg to
         // deal with security sensitive data.
         if (authReqMsg != null)
         {
            authReqMsg.cleanup();

            // Forget about the AuthReqMsg
            authReqMsg = null;
         }

         // Invoke the garbage collector to flush sensitive data
         System.gc();
      }
   }

   /**
    * Return the method id.
    *
    * @return Method id.
    */
   public final String getId()
   {
      return "Authenticate";
   }
}