/***********************************************************************
 * 
 *  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"
#include <micasa.h>

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

//
// Registry Key/Value defines used in the AuthCache
//
#define CASA_AUTH_CACHE_REG_KEY     "CASA_Auth_Cache"
#define CREATION_TIME_REG_VALUE     "CreationTime"
#define EXPIRATION_TIME_REG_VALUE   "ExpirationTime"
#define DOES_NOT_EXPIRE_REG_VALUE   "DoesNotExpire"
#define STATUS_REG_VALUE            "Status"
#define TOKEN_REG_VALUE             "Token"


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

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

// Non-host specific key name
static
char     g_allHosts[] = "AllHosts";

static
int      g_cacheEntryCount = 0;

HANDLE   g_hCASAContext;

//++=======================================================================
AuthCacheEntry*
CreateAuthTokenCacheEntry(
   IN    const char *pCacheKey,
   IN    const char *pGroupOrHostName,
   IN    CasaStatus status,
   IN    unsigned char *pToken,
   IN    int entryLifetime // seconds (0 == Lives forever)
   )
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   CasaStatus           retStatus;
   SSCS_KEYCHAIN_ID_T   sessionKeyChain = {26, "SSCS_SESSION_KEY_CHAIN_ID"};
   SSCS_SECRET_ID_T     sharedId = {27, "CASA_AUTHENTICATION_TOKENS"};
   uint32_t             tokenSize, entrySize, keySize;
   AuthCacheEntry       *pEntry = NULL;
   unsigned char     *pKey;


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

   if (status == CASA_STATUS_SUCCESS)
   {
      tokenSize = (uint32_t)strlen(pToken);
   }
   else
   {
      tokenSize = 0;
   }

   entrySize = tokenSize + sizeof(AuthCacheEntry);

   // Allocate space for the entry
   // The AuthCacheEntry structure contains room for the tokens NULL terminator
   pEntry = (AuthCacheEntry*) malloc(entrySize);
   if (pEntry)
   {
      // Set the status
      pEntry->status = status;
      
      if (pEntry->status == CASA_STATUS_SUCCESS)
      {
         memcpy(&pEntry->token[0], pToken, tokenSize);
      }
      
      pEntry->token[tokenSize] = '\0';
      
      // Set the time when the entry was added to the cache
      pEntry->creationTime = GetTickCount();
      
      // First determine the time when the entry is due to expire
      if (entryLifetime != 0)
      {
         pEntry->expirationTime = pEntry->creationTime + (entryLifetime * 1000);
         pEntry->doesNotExpire = FALSE;
      }
      else
      {
         // The entry does not expire
         pEntry->expirationTime = 0;
         pEntry->doesNotExpire = TRUE;
      }
      
      keySize = (uint32_t)strlen(pCacheKey) + (uint32_t)strlen(pGroupOrHostName) + 2;
      
      pKey = malloc(keySize);
      
      if (pKey)
      {
         strncpy(pKey, pCacheKey, keySize);
         strncat(pKey, "@", keySize);
         strncat(pKey, pGroupOrHostName, keySize);
      
         retStatus = miCASAWriteBinaryKey(g_hCASAContext,
                                          0,
                                          &sessionKeyChain,
                                          &sharedId,
                                          pKey,
                                          keySize,
                                          (uint8_t *)pEntry,
                                          &entrySize,
                                          NULL,
                                          NULL);
      
      
         free(pKey);
      }
      else
      {
         retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                     CASA_FACILITY_AUTHTOKEN,
                                     CASA_STATUS_INSUFFICIENT_RESOURCES);
      }
   }
   else
   {
      retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                  CASA_FACILITY_AUTHTOKEN,
                                  CASA_STATUS_INSUFFICIENT_RESOURCES);
   }

   DbgTrace(1, "-CreateAuthTokenCacheEntry- End, pEntry = %08X\n", pEntry);

   return pEntry;
}


//++=======================================================================
AuthCacheEntry*
CreateSessionTokenCacheEntry(
   IN    const char *pCacheKey,
   IN    CasaStatus status,
   IN    unsigned char *pToken,
   IN    int entryLifetime // seconds (0 == Lives forever)
   )
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   CasaStatus           retStatus;
   SSCS_KEYCHAIN_ID_T   sessionKeyChain = {26, "SSCS_SESSION_KEY_CHAIN_ID"};
   SSCS_SECRET_ID_T     sharedId = {20, "CASA_SESSION_TOKENS"};
   uint32_t             tokenSize, entrySize;
   AuthCacheEntry       *pEntry = NULL;


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

   if (status == CASA_STATUS_SUCCESS)
   {
      tokenSize = (uint32_t)strlen(pToken);
   }
   else
   {
      tokenSize = 0;
   }

   entrySize = tokenSize + sizeof(AuthCacheEntry);

   // Allocate space for the entry
   // The AuthCacheEntry structure contains room for the tokens NULL terminator
   pEntry = (AuthCacheEntry*) malloc(entrySize);
   if (pEntry)
   {
      // Set the status
      pEntry->status = status;
      
      if (pEntry->status == CASA_STATUS_SUCCESS)
      {
         memcpy(&pEntry->token[0], pToken, tokenSize);
      }
      
      pEntry->token[tokenSize] = '\0';
      
      // Set the time when the entry was added to the cache
      pEntry->creationTime = GetTickCount();
      
      // First determine the time when the entry is due to expire
      if (entryLifetime != 0)
      {
         pEntry->expirationTime = pEntry->creationTime + (entryLifetime * 1000);
         pEntry->doesNotExpire = FALSE;
      }
      else
      {
         // The entry does not expire
         pEntry->expirationTime = 0;
         pEntry->doesNotExpire = TRUE;
      }
      
      retStatus = miCASAWriteBinaryKey(g_hCASAContext,
                                       0,
                                       &sessionKeyChain,
                                       &sharedId,
                                       (char *)pCacheKey,
                                       (uint32_t)strlen(pCacheKey) + 1,
                                       (uint8_t *)pEntry,
                                       &entrySize,
                                       NULL,
                                       NULL);
   }
   else
   {
      retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                  CASA_FACILITY_AUTHTOKEN,
                                  CASA_STATUS_INSUFFICIENT_RESOURCES);
   }

   DbgTrace(1, "-CreateSessionTokenCacheEntry- End, pEntry = %08X\n", pEntry);

   return pEntry;
}


//++=======================================================================
void
FreeAuthCacheEntry(
   IN    AuthCacheEntry *pEntry
   )
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   DbgTrace(1, "-FreeAuthCacheEntry- Start, pEntry = %08X\n", pEntry);

   // Free the entry
   free(pEntry);

   DbgTrace(1, "-FreeAuthCacheEntry- End\n", 0);
}


//++=======================================================================
static
BOOL
CacheEntryLifetimeExpired(
   IN    DWORD creationTime,
   IN    DWORD expirationTime
   )
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   DWORD currentTime = GetTickCount();
   BOOL  expired = FALSE;

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

   // Check if the clock has wrapped
   if (currentTime >= creationTime)
   {
      // The clock has not wrapped, check if the
      // expiration time has wrapped.
      if (expirationTime > creationTime)
      {
         // The expiration time also has not wrapped,
         // do a straight compare against the current
         // time.
         if (currentTime >= expirationTime)
         {
            // It has expired
            expired = TRUE;
         }
      }
   }
   else
   {
      // The clock has wrapped, check if the expiration
      // time also wrapped.
      if (expirationTime > creationTime)
      {
         // The expiration time did not wrap, therefore
         // it has been exceeded since the clock wrapped.
         expired = TRUE;
      }
      else
      {
         // The expiration time also wrapped, do a straight
         // compare against the current time.
         if (currentTime >= expirationTime)
         {
            // It has expired
            expired = TRUE;
         }
      }
   }

   DbgTrace(2, "-CacheEntryLifetimeExpired- End, result = %08X\n", expired);

   return expired;
}


//++=======================================================================
AuthCacheEntry*
FindSessionTokenEntryInCache(
   IN    const char *pCacheKey
   )
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   CasaStatus           retStatus;
   SSCS_KEYCHAIN_ID_T   sessionKeyChain = {26, "SSCS_SESSION_KEY_CHAIN_ID"};
   SSCS_SECRET_ID_T     sharedId = {20, "CASA_SESSION_TOKENS"};
   uint32_t             valueLength, bytesRequired;
   AuthCacheEntry       *pEntry = NULL;


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

   valueLength = 0;
   bytesRequired = 0;

   retStatus = miCASAReadBinaryKey(g_hCASAContext,
                                   0,
                                   &sessionKeyChain,
                                   &sharedId,
                                   (char *)pCacheKey,
                                   (uint32_t)strlen(pCacheKey) + 1,
                                   NULL,
                                   &valueLength,
                                   NULL,
                                   &bytesRequired,
                                   NULL);

   if (retStatus == NSSCS_E_ENUM_BUFF_TOO_SHORT
       && bytesRequired != 0)
   {
      pEntry = (AuthCacheEntry*) malloc(bytesRequired);
      
      if (pEntry)
      {
         valueLength = bytesRequired;
         bytesRequired = 0;
         
         retStatus = miCASAReadBinaryKey(g_hCASAContext,
                                         0,
                                         &sessionKeyChain,
                                         &sharedId,
                                         (char *)pCacheKey,
                                         (uint32_t)strlen(pCacheKey) + 1,
                                         (uint8_t *)pEntry,
                                         &valueLength,
                                         NULL,
                                         &bytesRequired,
                                         NULL);
         if (CASA_SUCCESS(retStatus))
         {
            if (pEntry->doesNotExpire == FALSE
                && CacheEntryLifetimeExpired(pEntry->creationTime, pEntry->expirationTime))
            {
               // Remove the entry ???
               //miCASARemoveBinaryKey();
            
               retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                           CASA_FACILITY_AUTHTOKEN,
                                           CASA_STATUS_UNSUCCESSFUL);
            }
         }
         
         if (!CASA_SUCCESS(retStatus))
         {
            FreeAuthCacheEntry(pEntry);
            pEntry = NULL;
         }
      }
   }
              
   DbgTrace(1, "-FindSessionTokenEntryInCache- End, pEntry = %08X\n", pEntry);

   return pEntry;
}

//++=======================================================================
AuthCacheEntry*
FindAuthTokenEntryInCache(
   IN    const char *pCacheKey,
   IN    const char *pGroupOrHostName
   )
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   CasaStatus           retStatus;
   SSCS_KEYCHAIN_ID_T   sessionKeyChain = {26, "SSCS_SESSION_KEY_CHAIN_ID"};
   SSCS_SECRET_ID_T     sharedId = {27, "CASA_AUTHENTICATION_TOKENS"};
   uint32_t             valueLength, bytesRequired, keySize;
   AuthCacheEntry       *pEntry = NULL;
   unsigned char        *pKey;


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

   keySize = (uint32_t)strlen(pCacheKey) + (uint32_t)strlen(pGroupOrHostName) + 2;

   pKey = malloc(keySize);
   if (pKey)
   {
      strncpy(pKey, pCacheKey, keySize);
      strncat(pKey, "@", keySize);
      strncat(pKey, pGroupOrHostName, keySize);
      
      valueLength = 0;
      bytesRequired = 0;
      
      retStatus = miCASAReadBinaryKey(g_hCASAContext,
                                      0,
                                      &sessionKeyChain,
                                      &sharedId,
                                      pKey,
                                      keySize,
                                      NULL,
                                      &valueLength,
                                      NULL,
                                      &bytesRequired,
                                      NULL);
      
      if (retStatus == NSSCS_E_ENUM_BUFF_TOO_SHORT
          && bytesRequired != 0)
      {
         pEntry = (AuthCacheEntry*) malloc(bytesRequired);
         
         if (pEntry)
         {
            valueLength = bytesRequired;
            bytesRequired = 0;
            
            retStatus = miCASAReadBinaryKey(g_hCASAContext,
                                            0,
                                            &sessionKeyChain,
                                            &sharedId,
                                            pKey,
                                            keySize,
                                            (uint8_t *)pEntry,
                                            &valueLength,
                                            NULL,
                                            &bytesRequired,
                                            NULL);
            if (CASA_SUCCESS(retStatus))
            {
               if (pEntry->doesNotExpire == FALSE
                   && CacheEntryLifetimeExpired(pEntry->creationTime, pEntry->expirationTime))
               {
                 // Remove the entry ???
                 //miCASARemoveBinaryKey();
            
                 retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                             CASA_FACILITY_AUTHTOKEN,
                                             CASA_STATUS_UNSUCCESSFUL);
               }
            }
            
            if (!CASA_SUCCESS(retStatus))
            {
               FreeAuthCacheEntry(pEntry);
               pEntry = NULL;
            }
         }
      }
      
      free(pKey);
   }

   DbgTrace(1, "-FindAuthTokenEntryInCache- End, pEntry = %08X\n", pEntry);

   return pEntry;
}


//++=======================================================================
CasaStatus
InitializeAuthCache()
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   CasaStatus           retStatus;
   SSCS_SECRETSTORE_T   ssId;

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

   ssId.version = NSSCS_VERSION_NUMBER;
   strcpy((char *)ssId.ssName, (char *)SSCS_DEFAULT_SECRETSTORE_ID);

   g_hCASAContext = miCASAOpenSecretStoreCache(&ssId,
                                               0,
                                               NULL);
   if (!g_hCASAContext)
   {
      retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                 CASA_FACILITY_AUTHTOKEN,
                                 CASA_STATUS_UNSUCCESSFUL);
   }
   else
   {
      retStatus = CASA_STATUS_SUCCESS;
   }

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

   return retStatus;
}


//++=======================================================================
//++=======================================================================
//++=======================================================================