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

#define DEFAULT_RETRY_LIFETIME  5  // seconds

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

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

//
// Debug tracing level
// 
int   DebugLevel = 0;

//
// Operating parameter
// 
bool  secureRpcSetting = false;
int   retryLifetime = DEFAULT_RETRY_LIFETIME;


//++=======================================================================
static
CasaStatus
ObtainSessionToken(
   IN    RpcSession *pRpcSession,
   IN    char *pHostName,
   IN    AuthPolicy *pAuthPolicy,
   INOUT char **ppSessionToken)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L0
//=======================================================================--
{
   CasaStatus     retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                              CASA_FACILITY_AUTHTOKEN,
                                              CASA_STATUS_UNSUCCESSFUL);
   LIST_ENTRY     *pListEntry;
   AuthCacheEntry *pCacheEntry = NULL;

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

   // Initialize output parameter
   *ppSessionToken = NULL;

   // Look in our cache for an entry that matches one of the auth
   // contexts specified in the AuthPolicy object.
   pListEntry = pAuthPolicy->authContextListHead.Flink;
   while (pListEntry != &pAuthPolicy->authContextListHead)
   {
      AuthContext *pAuthContext;

      // Get pointer to AuthContext structure
      pAuthContext = CONTAINING_RECORD(pListEntry, AuthContext, listEntry);

      // Try to find a cache entry for the auth context
      pCacheEntry = FindEntryInAuthCache(pAuthContext->pContext, pHostName);
      if (pCacheEntry != NULL)
      {
         // Cache entry found, update the return status with the information
         // saved in it and stop looking.
         retStatus = pCacheEntry->status;
         break;
      }

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

   // If we did not find a cache entry that we can use, then Try to create one.
   pListEntry = pAuthPolicy->authContextListHead.Flink;
   while (!CASA_SUCCESS(retStatus)
          && pListEntry != &pAuthPolicy->authContextListHead)
   {
      AuthContext *pAuthContext;
      char        *pAuthMechToken;

      // Get pointer to AuthContext structure
      pAuthContext = CONTAINING_RECORD(pListEntry, AuthContext, listEntry);

      // Get authentication mechanism token
      retStatus = GetAuthMechToken(pAuthContext, &pAuthMechToken);
      if (!CASA_SUCCESS(retStatus))
      {
         // We were not able to obtain an authentication mechanism token
         // for the context.
         //
         // Advance to the next entry
         pListEntry = pListEntry->Flink;
         continue;
      }

      // Create a cache entry for the auth context
      pCacheEntry = CreateAuthCacheEntry(pAuthContext->pContext,  pHostName);
      if (pCacheEntry)
      {
         char  *pReqMsg = NULL;
         char  *pRespMsg = NULL;
         int   respLen;
         int   cacheEntryLifetime = retryLifetime;   // Initialize to retry in case of failure

         // Request auth token for the service
         pReqMsg = BuildAuthenticateMsg(pAuthContext, pAuthMechToken);
         if (pReqMsg)
         {
            // Issue rpc
            retStatus = Rpc(pRpcSession,
                            pAuthContext->pMechanism,
                            secureRpcSetting,
                            pReqMsg,
                            &pRespMsg,
                            &respLen);
            if (CASA_SUCCESS(retStatus))
            {
               AuthenticateResp     *pAuthenticateResp;

               // Create Authenticate response object
               retStatus = CreateAuthenticateResp(pRespMsg, respLen, &pAuthenticateResp);
               if (CASA_SUCCESS(retStatus))
               {
                  // Return the auth token to the caller
                  pCacheEntry->pToken = pAuthenticateResp->pToken;
                  pAuthenticateResp->pToken = NULL; // To keep us from freeing the buffer
                  cacheEntryLifetime = pAuthenticateResp->tokenLifetime;

                  // Free the Authenticate response object
                  RelAuthenticateResp(pAuthenticateResp);
               }
            }
            else
            {
               DbgTrace(0, "-ObtainSessionToken- Authenticate Rpc failure, error = %08X\n", retStatus);
            }
         }
         else
         {
            DbgTrace(0, "-ObtainSessionToken- Error building Authenticate msg\n", 0);
            retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                        CASA_FACILITY_AUTHTOKEN,
                                        CASA_STATUS_INSUFFICIENT_RESOURCES);
         }

         // Add the entry to the cache if successful or if the reason that we failed
         // was because the server was unavailable.
         if (CASA_SUCCESS(retStatus)
             || CasaStatusCode(retStatus) == CASA_STATUS_AUTH_SERVER_UNAVAILABLE)
         {
            pCacheEntry->status = retStatus;
            AddEntryToAuthCache(pCacheEntry, cacheEntryLifetime);
         }
         else
         {
            // Free the entry
            FreeAuthCacheEntry(pCacheEntry);
         }
      }
      else
      {
         DbgTrace(0, "-ObtainSessionToken- Cache entry creation failure\n", 0);
         retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                     CASA_FACILITY_AUTHTOKEN,
                                     CASA_STATUS_INSUFFICIENT_RESOURCES);

         // Stop trying after freeing up the buffer associated with
         // the authentication mechanism token.
         free(pAuthMechToken);
         break;
      }

      // Free up the buffer associated with the authentication mechanism token
      free(pAuthMechToken);

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

   // Return session token if successful
   if (CASA_SUCCESS(retStatus))
   {
      // Allocate a buffer for the return token
      *ppSessionToken = (char*) malloc(strlen(pCacheEntry->pToken) + 1);
      if (*ppSessionToken)
      {
         // Copy the token onto the allocated buffer
         strcpy(*ppSessionToken, pCacheEntry->pToken);
      }
      else
      {
         DbgTrace(0, "-ObtainSessionToken- Buffer allocation failure\n", 0);
         retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                     CASA_FACILITY_AUTHTOKEN,
                                     CASA_STATUS_INSUFFICIENT_RESOURCES);
      }
   }

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

   return retStatus;
}


//++=======================================================================
static
CasaStatus
ObtainAuthTokenFromServer(
   IN    char *pServiceName,
   IN    char *pHostName,
   INOUT char **ppAuthToken,
   INOUT int *pTokenLifetime)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L0
//=======================================================================--
{
   CasaStatus  retStatus = CASA_STATUS_SUCCESS;
   RpcSession  *pRpcSession;

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

   // Initialize output parameter
   *ppAuthToken = NULL;

   // Open Rpc Session to the auth service at the specified host
   pRpcSession = OpenRpcSession(pHostName);
   if (pRpcSession)
   {
      char                 *pReqMsg = NULL;
      char                 *pRespMsg = NULL;
      int                  respLen;
      AuthPolicy           *pAuthPolicy = NULL;
      GetAuthPolicyResp    *pGetAuthPolicyResp = NULL;
      GetAuthTokenResp     *pGetAuthTokenResp = NULL;
      char                 *pSessionToken = NULL;

      // Request the auth parameters associated with this service
      pReqMsg = BuildGetAuthPolicyMsg(pServiceName, pHostName);
      if (pReqMsg)
      {
         // Issue rpc
         retStatus = Rpc(pRpcSession,
                         "GetAuthPolicy",
                         secureRpcSetting,
                         pReqMsg,
                         &pRespMsg,
                         &respLen);
         if (CASA_SUCCESS(retStatus))
         {
            // Create GetAuthPolicy response object
            retStatus = CreateGetAuthPolicyResp(pRespMsg, respLen, &pGetAuthPolicyResp);
            if (CASA_SUCCESS(retStatus))
            {
               // Create the AuthPolicy object
               retStatus = CreateAuthPolicy(pGetAuthPolicyResp->pPolicy,
                                            pGetAuthPolicyResp->policyLen,
                                            &pAuthPolicy);
               if (CASA_SUCCESS(retStatus))
               {
                  // Now try to obtain a session token
                  retStatus = ObtainSessionToken(pRpcSession, pHostName, pAuthPolicy, &pSessionToken);
                  if (CASA_SUCCESS(retStatus))
                  {
                     // Request auth token for the service
                     free(pReqMsg);
                     pReqMsg = BuildGetAuthTokenMsg(pServiceName, pHostName, pSessionToken);
                     if (pReqMsg)
                     {
                        // Free the previous response msg buffer
                        free(pRespMsg);
                        pRespMsg = NULL;

                        // Issue rpc
                        retStatus = Rpc(pRpcSession,
                                        "GetAuthToken",
                                        secureRpcSetting,
                                        pReqMsg,
                                        &pRespMsg,
                                        &respLen);
                        if (CASA_SUCCESS(retStatus))
                        {
                           // Create GetAuthPolicy response object
                           retStatus = CreateGetAuthTokenResp(pRespMsg, respLen, &pGetAuthTokenResp);
                           if (CASA_SUCCESS(retStatus))
                           {
                              // Return the auth token to the caller
                              *ppAuthToken = pGetAuthTokenResp->pToken;
                              pGetAuthTokenResp->pToken = NULL; // To keep us from freeing the buffer
                              *pTokenLifetime = pGetAuthTokenResp->tokenLifetime;
                           }
                        }
                        else
                        {
                           DbgTrace(0, "-ObtainAuthTokenFromServer- GetAuthToken Rpc failure, error = %08X\n", retStatus);
                        }
                     }
                     else
                     {
                        DbgTrace(0, "-ObtainAuthTokenFromServer- Error building GetAuthToken msg\n", 0);
                        retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                                    CASA_FACILITY_AUTHTOKEN,
                                                    CASA_STATUS_INSUFFICIENT_RESOURCES);
                     }
                  }
                  else
                  {
                     DbgTrace(1, "-ObtainAuthTokenFromServer- Failed to obtain session token, error = %08X\n", retStatus);
                  }
               }
            }
         }
         else
         {
            DbgTrace(0, "-ObtainAuthTokenFromServer- GetAuthPolicy Rpc failure, error = %08X\n", retStatus);
         }

         // Free resources that may be hanging around
         if (pReqMsg)
            free(pReqMsg);

         if (pRespMsg)
            free(pRespMsg);

         if (pSessionToken)
            free(pSessionToken);

         if (pGetAuthTokenResp)
            RelGetAuthTokenResp(pGetAuthTokenResp);

         if (pGetAuthPolicyResp)
            RelGetAuthPolicyResp(pGetAuthPolicyResp);

         if (pAuthPolicy)
            RelAuthPolicy(pAuthPolicy);
      }
      else
      {
         DbgTrace(0, "-ObtainAuthTokenFromServer- Error building GetAuthPolicy msg\n", 0);
         retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                     CASA_FACILITY_AUTHTOKEN,
                                     CASA_STATUS_INSUFFICIENT_RESOURCES);
      }

      // Close the Rpc Session
      CloseRpcSession(pRpcSession);
   }
   else
   {
      DbgTrace(0, "-ObtainAuthTokenFromServer- Error opening Rpc session\n", 0);
      retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                  CASA_FACILITY_AUTHTOKEN,
                                  CASA_STATUS_INSUFFICIENT_RESOURCES);
   }

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

   return retStatus;
}


//++=======================================================================
CasaStatus SSCS_CALL
ObtainAuthToken(
   IN    const char *pServiceAtHostName,
   INOUT char *pAuthTokenBuf,
   INOUT int *pAuthTokenBufLen)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L0
//=======================================================================--
{
   CasaStatus  retStatus = CASA_STATUS_SUCCESS;
   char        *pParseString;

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

   // Verify the input parameters
   if (pServiceAtHostName == NULL
      || pAuthTokenBufLen == NULL
      || (*pAuthTokenBufLen != 0 && pAuthTokenBuf == NULL))
   {
      DbgTrace(0, "-ObtainAuthToken- Invalid parameter\n", 0);
      retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                  CASA_FACILITY_AUTHTOKEN,
                                  CASA_STATUS_INVALID_PARAMETER);
      goto exit;
   }

   // Allocate space to copy the service name string
   pParseString = (char*) malloc(strlen(pServiceAtHostName) + 1);
   if (pParseString)
   {
      char  *pServiceName, *pHostName;

      // Space allocated, now copy the string onto it
      // and parse it into its components.
      strcpy(pParseString, pServiceAtHostName);
      pServiceName = strtok(pParseString, "@");
      pHostName = strtok(NULL, "@");
      if (pHostName == NULL)
      {
         DbgTrace(0, "-ObtainAuthToken- Missing host name\n", 0);
         retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                     CASA_FACILITY_AUTHTOKEN,
                                     CASA_STATUS_INVALID_PARAMETER);
      }
      else
      {
         AuthCacheEntry *pCacheEntry;
         char           *pNormalizedHostName;

         // Normalize the host name
         pNormalizedHostName = NormalizeHostName(pHostName);
         if (pNormalizedHostName)
         {
            // Start user process synchronization
            LockUserMutex();

            // Try to find a cache entry for the service
            pCacheEntry = FindEntryInAuthCache(pServiceName, pNormalizedHostName);
            if (pCacheEntry == NULL)
            {
               // No entry found in the cache, create one.
               pCacheEntry = CreateAuthCacheEntry(pServiceName, pNormalizedHostName);
               if (pCacheEntry)
               {
                  int   cacheEntryLifetime = retryLifetime; // Initialize to retry in case of failure

                  // Cache entry created, now try to obtain auth token from the CASA Server
                  retStatus = ObtainAuthTokenFromServer(pServiceName,
                                                        pNormalizedHostName,
                                                        &pCacheEntry->pToken,
                                                        &cacheEntryLifetime);

                  // Add the entry to the cache if successful or if the reason that we failed
                  // was because the server was un-available.
                  if (CASA_SUCCESS(retStatus)
                      || CasaStatusCode(retStatus) == CASA_STATUS_AUTH_SERVER_UNAVAILABLE)
                  {
                     pCacheEntry->status = retStatus;
                     AddEntryToAuthCache(pCacheEntry, cacheEntryLifetime);
                  }
                  else
                  {
                     // Free the entry
                     FreeAuthCacheEntry(pCacheEntry);
                  }
               }
               else
               {
                  DbgTrace(0, "-ObtainAuthToken- Cache entry creation failure\n", 0);
                  retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                              CASA_FACILITY_AUTHTOKEN,
                                              CASA_STATUS_INSUFFICIENT_RESOURCES);
               }
            }
            else
            {
               // Cache entry found, update the return status with the information saved in it.
               retStatus = pCacheEntry->status;
            }

            // Try to return auth token if we have one to return
            if (CASA_SUCCESS(retStatus))
            {
               int   tokenLen = (int) strlen(pCacheEntry->pToken) + 1;

               // We have an authentication token, try to return it to the caller.
               if (pAuthTokenBuf)
               {
                  // Verify that the supplied buffer is big enough
                  if (*pAuthTokenBufLen >= tokenLen)
                  {
                     // Return the auth token to the caller
                     strcpy(pAuthTokenBuf, pCacheEntry->pToken);
                  }
                  else
                  {
                     DbgTrace(0, "-ObtainAuthToken- The supplied buffer is not large enough", 0);
                     retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                                 CASA_FACILITY_AUTHTOKEN,
                                                 CASA_STATUS_BUFFER_OVERFLOW);
                  }

                  // Notify the caller about the token length
                  *pAuthTokenBufLen = tokenLen;
               }
               else
               {
                  // The caller just wants the length of buffer that is required to
                  // obtain the token.
                  *pAuthTokenBufLen = tokenLen;
               }
            }

            // Stop user process synchronization
            FreeUserMutex();

            // Free the space allocated for the normalized host name
            free(pNormalizedHostName);
         }
         else
         {
            DbgTrace(0, "-ObtainAuthToken- Host name normalization failed\n", 0);
            retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                        CASA_FACILITY_AUTHTOKEN,
                                        CASA_STATUS_UNSUCCESSFUL);
         }
      }

      // Free allocated space
      free(pParseString);
   }
   else
   {
      DbgTrace(0, "-ObtainAuthToken- Buffer allocation error\n", 0);
   }

exit:

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

   return retStatus;
}


//++=======================================================================
int
InitializeLibrary(void)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L0
//=======================================================================--
{
   int   retStatus = -1;

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

   // Create user synchronization mutex
   if (CreateUserMutex() == 0)
   {
      // Initialize the auth cache
      if (CASA_SUCCESS(InitializeAuthCache()))
      {
         // Initialize the host name normalization
         if (CASA_SUCCESS(InitializeHostNameNormalization()))
         {
            // Success
            retStatus = 0;
         }
         else
         {
            DbgTrace(0, "-InitializeLibrary- Error initializing host name normalization\n", 0);
         }
      }
      else
      {
         DbgTrace(0, "-InitializeLibrary- Error initializing the auth cache\n", 0);
      }
   }
   else
   {
      DbgTrace(0, "-InitializeLibrary- Error creating mutex for the user\n", 0);
   }

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

   return retStatus;
}