/***********************************************************************
 * 
 *  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>
 *
 ***********************************************************************/


//===[ Include files ]=====================================================

#include "internal.h"

//===[ Type definitions ]==================================================

//
// AuthMechMod definition
// 
typedef struct _AuthMechMod
{
   LIST_ENTRY        listEntry;
   char              *pAuthTypeName;
   int               authTypeNameLen;
   LIB_HANDLE        libHandle;
   AuthTokenIf       *pAuthTokenIf;

} AuthMechMod, *PAuthMechMod;


//===[ Function prototypes ]===============================================

//===[ Global variables ]==================================================

// AuthMechModule List and syncronizing mutex
static
LIST_ENTRY  g_authMechModuleListHead = {&g_authMechModuleListHead,
                                        &g_authMechModuleListHead};


//++=======================================================================
static
CasaStatus
GetAuthTokenIf(
   IN       const char        *pAuthTypeName,
   INOUT    AuthTokenIf   **ppAuthTokenIf)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
//  Environment:
//
// L2
//=======================================================================--
{
   CasaStatus  retStatus;
   ConfigIf    *pModuleConfigIf;


   DbgTrace(2, "-GetAuthTokenIf- Start\n", 0);

   // Get the configuration for the module
   retStatus = GetConfigInterface(mechConfigFolder,
                                  pAuthTypeName,
                                  &pModuleConfigIf);
   if (CASA_SUCCESS(retStatus)
       && CasaStatusCode(retStatus) != CASA_STATUS_OBJECT_NOT_FOUND)
   {
      LIST_ENTRY     *pListEntry;
      AuthMechMod    *pAuthMechMod = NULL;
      int32_t        authTypeNameLen = (int32_t) strlen(pAuthTypeName);

      // Look if we already have the module in our list
      pListEntry = g_authMechModuleListHead.Flink;
      while (pListEntry != &g_authMechModuleListHead)
      {
         // Get pointer to the current entry
         pAuthMechMod = CONTAINING_RECORD(pListEntry, AuthMechMod, listEntry);

         // Check if this is the module that we need
         if (pAuthMechMod->authTypeNameLen == authTypeNameLen
             && memcmp(pAuthTypeName, pAuthMechMod->pAuthTypeName, authTypeNameLen) == 0)
         {
            // This is the module that we need, stop looking.
            break;
         }
         else
         {
            // This is not the module that we are looking for
            pAuthMechMod = NULL;
         }

         // Advance to the next entry
         pListEntry = pListEntry->Flink;
      }

      // Proceed based on whether or not a module was found
      if (pAuthMechMod)
      {
         // Module found in our list, provide the caller with its AuthTokenIf
         // instance after we have incremented its reference count.
         pAuthMechMod->pAuthTokenIf->addReference(pAuthMechMod->pAuthTokenIf);
         *ppAuthTokenIf = pAuthMechMod->pAuthTokenIf;

         // Success
         retStatus = CASA_STATUS_SUCCESS;
      }
      else
      {
         // Needed module not found in our list, create an entry.
         pAuthMechMod = (AuthMechMod*) malloc(sizeof(*pAuthMechMod));
         if (pAuthMechMod)
         {
            // Allocate buffer to contain the authentication type name within the module entry
            pAuthMechMod->pAuthTypeName = (char*) malloc(authTypeNameLen + 1);
            if (pAuthMechMod->pAuthTypeName)
            {
               char  *pLibraryName;

               // Initialize the library handle field
               pAuthMechMod->libHandle = NULL;

               // Save the auth type name within the entry
               strcpy(pAuthMechMod->pAuthTypeName, pAuthTypeName);
               pAuthMechMod->authTypeNameLen = authTypeNameLen;

               // Obtain the name of the library that we must load
               pLibraryName = pModuleConfigIf->getEntryValue(pModuleConfigIf, "LibraryName");
               if (pLibraryName)
               {
                  // Load the library
                  pAuthMechMod->libHandle = OpenLibrary(pLibraryName);
                  if (pAuthMechMod->libHandle)
                  {
                     PFN_GetAuthTokenIfRtn   pGetAuthTokenIfRtn;

                     // Library has been loaded, now get a pointer to its GetAuthTokenInterface routine
                     pGetAuthTokenIfRtn = (PFN_GetAuthTokenIfRtn) GetFunctionPtr(pAuthMechMod->libHandle,
                                                                                 GET_AUTH_TOKEN_INTERFACE_RTN_SYMBOL);
                     if (pGetAuthTokenIfRtn)
                     {
                        // Now, obtain the modules AuthTokenIf.
                        retStatus = (pGetAuthTokenIfRtn)(pModuleConfigIf, &pAuthMechMod->pAuthTokenIf);
                     }
                     else
                     {
                        DbgTrace(0, "-GetAuthTokenIf- GetFunctionPtr\n", 0);
                        retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                                    CASA_FACILITY_AUTHTOKEN,
                                                    CASA_STATUS_LIBRARY_LOAD_FAILURE);
                     }
                  }
                  else
                  {
                     DbgTrace(0, "-GetAuthTokenIf- OpenLibrary error\n", 0);
                     retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                                 CASA_FACILITY_AUTHTOKEN,
                                                 CASA_STATUS_UNSUCCESSFUL);
                  }

                  // Free the buffer holding the library name
                  free(pLibraryName);
               }
               else
               {
                  DbgTrace(0, "-GetAuthTokenIf- Library name not configured\n", 0);
                  retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                              CASA_FACILITY_AUTHTOKEN,
                                              CASA_STATUS_CONFIGURATION_ERROR);
               }

               // Check if we were successful at obtaining the AuthTokenIf instance for the
               // module.
               if (CASA_SUCCESS(retStatus))
               {
                  // Insert the entry in the list, provide the caller with its AuthTokenIf
                  // instance after we have incremented its reference count.
                  InsertTailList(&g_authMechModuleListHead, &pAuthMechMod->listEntry);
                  pAuthMechMod->pAuthTokenIf->addReference(pAuthMechMod->pAuthTokenIf);
                  *ppAuthTokenIf = pAuthMechMod->pAuthTokenIf;
               }
               else
               {
                  // Failed, free resources.
                  free(pAuthMechMod->pAuthTypeName);
                  if (pAuthMechMod->libHandle)
                     CloseLibrary(pAuthMechMod->libHandle);
                  free(pAuthMechMod);
               }
            }
            else
            {
               DbgTrace(0, "GetAuthTokenIf-GetAuthTokenIf- Unable to allocate buffer\n", 0);

               // Free buffer allocated for entry
               free(pAuthMechMod);

               retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                           CASA_FACILITY_AUTHTOKEN,
                                           CASA_STATUS_INSUFFICIENT_RESOURCES);
            }
         }
         else
         {
            DbgTrace(0, "-GetAuthTokenIf- Unable to allocate buffer\n", 0);
            retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                        CASA_FACILITY_AUTHTOKEN,
                                        CASA_STATUS_INSUFFICIENT_RESOURCES);
         }
      }

      // Release config interface instance
      pModuleConfigIf->releaseReference(pModuleConfigIf);
   }
   else
   {
      DbgTrace(0, "-GetAuthTokenIf- Unable to obtain config interface\n", 0);
      retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                  CASA_FACILITY_AUTHTOKEN,
                                  CASA_STATUS_CONFIGURATION_ERROR);
   }

   DbgTrace(2, "-GetAuthTokenIf- End, retStatus = %08X\n", retStatus);

   return retStatus;
}


//++=======================================================================
CasaStatus
GetAuthMechToken(
   IN    AuthContext *pAuthContext,
   INOUT char **ppAuthToken)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L0
//=======================================================================--
{
   CasaStatus        retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                                 CASA_FACILITY_AUTHTOKEN,
                                                 CASA_STATUS_UNSUCCESSFUL);
   AuthTokenIf   *pAuthTokenIf;

   DbgTrace(1, "-GetAuthMechToken- Start\n", 0);

   // Initialize output parameter
   *ppAuthToken = NULL;

   // Obtain the appropriate token interface for the authentication type
   retStatus = GetAuthTokenIf(pAuthContext->pMechanism,
                                  &pAuthTokenIf);
   if (CASA_SUCCESS(retStatus))
   {
      char  *pAuthToken = NULL;
      int   authTokenBufLen = 0;

      // We found a provider for the service, query it for the buffer size
      // needed to obtain the authentication token.
      retStatus = pAuthTokenIf->getAuthToken(pAuthTokenIf,
                                                 pAuthContext->pContext,
                                                 pAuthContext->pMechInfo,
                                                 pAuthToken,
                                                 &authTokenBufLen);
      if (CasaStatusCode(retStatus) == CASA_STATUS_BUFFER_OVERFLOW)
      {
         // Allocate buffer to hold the authentication token
         pAuthToken = (char*) malloc(authTokenBufLen);
         if (pAuthToken)
         {
            // Request the token from the provider
            retStatus = pAuthTokenIf->getAuthToken(pAuthTokenIf,
                                                       pAuthContext->pContext,
                                                       pAuthContext->pMechInfo,
                                                       pAuthToken,
                                                       &authTokenBufLen);
            if (CASA_SUCCESS(retStatus))
            {
               // Return the buffer containing the token to the caller
               *ppAuthToken = pAuthToken;
            }
            else
            {
               // Free the allocated buffer
               free(pAuthToken);
            }
         }
         else
         {
            DbgTrace(0, "-GetAuthMechToken- Buffer allocation failure\n", 0);
         }
      }

      // Release token interface
      pAuthTokenIf->releaseReference(pAuthTokenIf);
   }
   else
   {
      // No authentication token interface available for authentication type
      DbgTrace(0, "-GetAuthMechToken- Failed to obtain auth mech token interface\n", 0);
   }

   DbgTrace(1, "-GetAuthMechToken- End, retStatus = %08X\n", retStatus);

   return retStatus;
}