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

#define BAD_CACHE_TRIGER_TIME    30 // seconds

#define DEFAULT_ATS_PORT 2645

#define LOG_FILE_NAME "\\casaauthtoken.log"

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

int
InitializeLibrary(void);

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

//
// Debug tracing level and debug log file path.
//
int   DebugLevel = 0;
char  *g_pDebugLogFilePath = NULL;

//
// Operating parameter
// 
bool        g_bInitialized = false;
long        g_rpcFlags = SECURE_RPC_FLAG | ALLOW_INVALID_CERTS_USER_APPROVAL_RPC_FLAG;
LIST_ENTRY  g_ATSHostList;


//++=======================================================================
static
CasaStatus
ObtainSessionToken(
   IN    RpcSession *pRpcSession,
   IN    AuthPolicy *pAuthPolicy,
   IN    const char *pHostName,
   IN    void *pCredStoreScope,
   INOUT char **ppSessionToken,
   INOUT AuthContext **ppSessionTokenAuthContext)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   CasaStatus        retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                                 CASA_FACILITY_AUTHTOKEN,
                                                 CASA_STATUS_UNSUCCESSFUL);
   LIST_ENTRY        *pListEntry;
   AuthCacheEntry    *pCacheEntry = NULL;
   AuthContext       *pAuthContext = 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)
   {
      // Get pointer to AuthContext structure
      pAuthContext = CONTAINING_RECORD(pListEntry, AuthContext, listEntry);

      // Try to find a cache entry for the auth context
      pCacheEntry = FindSessionTokenEntryInCache(pAuthContext->pContext,
                                                 pCredStoreScope);
      if (pCacheEntry != NULL)
      {
         // Cache entry found, check if it is of use to us.
         if (CASA_SUCCESS(pCacheEntry->status))
         {
            // This entry can be used, stop looking.
            retStatus = pCacheEntry->status;
            break;
         }
         else
         {
            // Free the entry
            FreeAuthCacheEntry(pCacheEntry);
         }
      }

      // 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)
   {
      char  *pAuthMechToken;

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

      // Only try to create cache entry for the auth context if there is not
      // one already.
      pCacheEntry = FindSessionTokenEntryInCache(pAuthContext->pContext,
                                                 pCredStoreScope);
      if (pCacheEntry == NULL)
      {
         char     *pReqMsg = NULL;
         char     *pRespMsg = NULL;
         size_t   respLen;

         // Get authentication mechanism token
         retStatus = GetAuthMechToken(pAuthContext,
                                      pHostName,
                                      pCredStoreScope,
                                      &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;
         }

         // Authenticate to the ATS
         pReqMsg = BuildAuthenticateMsg(pAuthContext, pAuthMechToken);
         if (pReqMsg)
         {
            // Issue rpc
            retStatus = Rpc(pRpcSession,
                            "Authenticate",
                            g_rpcFlags,
                            pReqMsg,
                            &pRespMsg,
                            &respLen);
            if (CASA_SUCCESS(retStatus))
            {
               if (pRespMsg
                   && respLen != 0)
               {
                  AuthenticateResp     *pAuthenticateResp;

                  // Create Authenticate response object
                  retStatus = CreateAuthenticateResp(pRespMsg, respLen, &pAuthenticateResp);
                  if (CASA_SUCCESS(retStatus))
                  {
                     // Return the auth token to the caller
                     pCacheEntry = CreateSessionTokenCacheEntry(pAuthContext->pContext,
                                                                retStatus,
                                                                pAuthenticateResp->pToken,
                                                                pAuthenticateResp->tokenLifetime,
                                                                pCredStoreScope);

                     pAuthenticateResp->pToken = NULL; // To keep us from freeing the buffer

                     // Free the Authenticate response object
                     RelAuthenticateResp(pAuthenticateResp);
                  }
               }
               else
               {
                  DbgTrace(0, "-ObtainSessionToken- Did not receive Authenticate Response data\n", 0);
                  retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                              CASA_FACILITY_AUTHTOKEN,
                                              CASA_STATUS_SERVER_ERROR);
               }
            }
            else
            {
               DbgTrace(0, "-ObtainSessionToken- Authenticate Rpc failure, error = %08X\n", retStatus);
            }

            // Free resources that may be hanging around
            if (pRespMsg)
            {
               // Clear and free the memory associated with the response since it may contain
               // security sensitive data.
               memset(pRespMsg, 0, respLen);
               free(pRespMsg);
            }

            // Clear and free the memory associated with the request message since
            // it may contain security sensitive information.
            memset(pReqMsg, 0, strlen(pReqMsg));
            free(pReqMsg);
         }
         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 the reason that we failed was because
         // the server was unavailable.
         if (CasaStatusCode(retStatus) == CASA_STATUS_AUTH_SERVER_UNAVAILABLE)
         {
            pCacheEntry = CreateSessionTokenCacheEntry(pAuthContext->pContext,
                                                       retStatus,
                                                       NULL,
                                                       DEFAULT_RETRY_LIFETIME,
                                                       pCredStoreScope);
   
         }
   
         // Release the cache entry if the resulting status is not successful
         if (pCacheEntry)
         {
            if (!CASA_SUCCESS(retStatus))
            {
               FreeAuthCacheEntry(pCacheEntry);
            }
         }

         // Free up the buffer associated with the authentication mechanism token
         // after clearing it since it may contain sensitive information.
         memset(pAuthMechToken, 0, strlen(pAuthMechToken));
         free(pAuthMechToken);
      }
      else
      {
         // Free the entry
         FreeAuthCacheEntry(pCacheEntry);
      }

      // 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->token) + 1);
      if (*ppSessionToken)
      {
         // Copy the token onto the allocated buffer
         strcpy(*ppSessionToken, pCacheEntry->token);

         // Return pointer to AuthContext associated with the session token
         *ppSessionTokenAuthContext = pAuthContext;
      }
      else
      {
         DbgTrace(0, "-ObtainSessionToken- Buffer allocation failure\n", 0);
         retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                     CASA_FACILITY_AUTHTOKEN,
                                     CASA_STATUS_INSUFFICIENT_RESOURCES);
      }

      FreeAuthCacheEntry(pCacheEntry);
   }

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

   return retStatus;
}


//++=======================================================================
static
CasaStatus
ObtainAuthTokenFromServer(
   IN    const char *pServiceName,
   IN    const char *pHostName,
   IN    const char *pNormalizedHostName,
   IN    const ATSHostEntry *pATSHost,
   IN    const void *pCredStoreScope,
   INOUT char **ppAuthToken,
   INOUT int *pTokenLifetime,
   INOUT bool *pAdvisedToRetry)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   CasaStatus  retStatus = CASA_STATUS_SUCCESS;
   RpcSession  *pRpcSession;

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

   // Initialize output parameters
   *ppAuthToken = NULL;
   *pAdvisedToRetry = false;

   // Open Rpc Session to the auth service at the specified host
   pRpcSession = OpenRpcSession(pATSHost->pName,
                                pATSHost->port);
   if (pRpcSession)
   {
      char                 *pReqMsg = NULL;
      char                 *pRespMsg = NULL;
      size_t               respLen;
      AuthPolicy           *pAuthPolicy = NULL;
      GetAuthPolicyResp    *pGetAuthPolicyResp = NULL;
      GetAuthTokenResp     *pGetAuthTokenResp = NULL;
      char                 *pSessionToken = NULL;

      // Request the auth parameters associated with this service
      if (strcmp(pHostName, pATSHost->pName) == 0
          || strcmp(pNormalizedHostName, pATSHost->pName) == 0)
      {
         pReqMsg = BuildGetAuthPolicyMsg(pServiceName, "localhost");
      }
      else
      {
         pReqMsg = BuildGetAuthPolicyMsg(pServiceName, pNormalizedHostName);
      }
      if (pReqMsg)
      {
         // Issue rpc
         retStatus = Rpc(pRpcSession,
                         "GetAuthPolicy",
                         g_rpcFlags,
                         pReqMsg,
                         &pRespMsg,
                         &respLen);
         if (CASA_SUCCESS(retStatus))
         {
            if (pRespMsg
                && respLen != 0)
            {
               // 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))
                  {
                     AuthContext *pSessionTokenAuthContext = NULL;

                     // Now try to obtain a session token
                     retStatus = ObtainSessionToken(pRpcSession,
                                                    pAuthPolicy,
                                                    (const char*) pATSHost->pName,
                                                    pCredStoreScope,
                                                    &pSessionToken,
                                                    &pSessionTokenAuthContext);
                     if (CASA_SUCCESS(retStatus))
                     {
                        free(pReqMsg);

                        // Request auth token for the service
                        if (strcmp(pHostName, pATSHost->pName) == 0
                            || strcmp(pNormalizedHostName, pATSHost->pName) == 0)
                        {
                           pReqMsg = BuildGetAuthTokenMsg(pServiceName, "localhost", pSessionToken);
                        }
                        else
                        {
                           pReqMsg = BuildGetAuthTokenMsg(pServiceName, pNormalizedHostName, pSessionToken);
                        }
                        if (pReqMsg)
                        {
                           // Free the previous response msg buffer
                           free(pRespMsg);
                           pRespMsg = NULL;

                           // Issue rpc
                           retStatus = Rpc(pRpcSession,
                                           "GetAuthToken",
                                           g_rpcFlags,
                                           pReqMsg,
                                           &pRespMsg,
                                           &respLen);
                           if (CASA_SUCCESS(retStatus))
                           {
                              if (pRespMsg
                                  && respLen != 0)
                              {
                                 // 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- Failed to create GetAuthTokenResp object, error = %08X\n", retStatus);

                                    // Remove the session token from the cache in case that it was due to a bad session token
                                    if (pSessionTokenAuthContext)
                                    {
                                       RemoveSessionTokenEntryInCache(pSessionTokenAuthContext->pContext,
                                                                      pCredStoreScope);

                                       // Advice that a retry should be attempted
                                       *pAdvisedToRetry = true;
                                    }
                                 }
                              }
                              else
                              {
                                 DbgTrace(0, "-ObtainAuthTokenFromServer- Did not receive GetAuthToken Response data\n", 0);
                                 retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                                             CASA_FACILITY_AUTHTOKEN,
                                                             CASA_STATUS_SERVER_ERROR);
                              }
                           }
                           else
                           {
                              DbgTrace(0, "-ObtainAuthTokenFromServer- GetAuthToken Rpc failure, error = %08X\n", retStatus);
                              retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                                          CASA_FACILITY_AUTHTOKEN,
                                                          CASA_STATUS_SERVER_ERROR);
                           }
                        }
                        else
                        {
                           DbgTrace(0, "-ObtainAuthTokenFromServer- Error building GetAuthToken msg\n", 0);
                           retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                                       CASA_FACILITY_AUTHTOKEN,
                                                       CASA_STATUS_INSUFFICIENT_RESOURCES);
                        }
                     }
                     else
                     {
                        DbgTrace(0, "-ObtainAuthTokenFromServer- Failed to obtain session token, error = %08X\n", retStatus);
                     }
                  }
                  else
                  {
                     DbgTrace(0, "-ObtainAuthTokenFromServer- Failed to create AuthPolicy object, error = %08X\n", retStatus);
                  }
               }
               else
               {
                  DbgTrace(0, "-ObtainAuthTokenFromServer- Failed to create GetAuthPolicyResp object, error = %08X\n", retStatus);
               }
            }
            else
            {
               DbgTrace(0, "-ObtainAuthTokenFromServer- Did not receive GetAuthPolicy Response data\n", 0);
               retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                           CASA_FACILITY_AUTHTOKEN,
                                           CASA_STATUS_SERVER_ERROR);
            }
         }
         else
         {
            DbgTrace(0, "-ObtainAuthTokenFromServer- GetAuthPolicy Rpc failure, error = %08X\n", retStatus);
         }

         // Free resources that may be hanging around
         if (pReqMsg)
         {
            // Clear the memory before freeing up the request message since it
            // may contain security sensitive data.
            memset(pReqMsg, 0, strlen(pReqMsg));
            free(pReqMsg);
         }

         if (pRespMsg)
         {
            // Clear the memory before freeing up the response message since it
            // may contain security sensitive data.
            memset(pRespMsg, 0, respLen);
            free(pRespMsg);
         }

         if (pSessionToken)
         {
            // Clear the memory before freeing up the token since it is
            // security sensitive data.
            memset(pSessionToken, 0, strlen(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
ObtainAuthTokenInt(
   IN    const char *pServiceName,
   IN    const char *pHostName,
   IN    const void *pCredStoreScope,
   INOUT char *pAuthTokenBuf,
   INOUT int *pAuthTokenBufLen)
//
//  Arguments: 
//    pServiceName -
//       Pointer to NULL terminated string that contains the
//       name of the service to which the client is trying to
//       authenticate.
//               
//    pHostName -
//       Pointer to NULL terminated string that contains the
//       name of the host where resides the service to which the
//       client is trying to authenticate. Note that the name
//       can either be a DNS name or a dotted IP address.
//               
//    pCredStoreScope -
//       Pointer to CASA structure for scoping credential store access
//       to specific users. This can only be leveraged by applications
//       running in the context of System.
//   
//    pAuthTokenBuf -
//       Pointer to buffer that will receive the authentication
//       token. The length of this buffer is specified by the
//       pAuthTokenBufLen parameter. Note that the the authentication
//       token will be in the form of a NULL terminated string.
//
//    pAuthTokenBufLen -
//       Pointer to integer that contains the length of the
//       buffer pointed at by pAuthTokenBuf. Upon return of the
//       function, the integer will contain the actual length
//       of the authentication token if the function successfully
//       completes or the buffer length required if the function
//       fails because the buffer pointed at by pAuthTokenBuf is
//       not large enough.
//
// Returns:
//    Casa Status
//                           
// Description:
//    Get authentication token to authenticate user to specified
//    service at host. The user is scoped using the info associated
//    with the magic cookie.
//
// L2
//=======================================================================--
{
   CasaStatus        retStatus = CASA_STATUS_SUCCESS;
   AuthCacheEntry    *pCacheEntry;
   char              *pNormalizedHostName;
   char              *pToken;
   HANDLE            hUserMutex = NULL;

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

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

   DbgTrace(1, "-ObtainAuthTokenInt- ServiceName = %s\n", pServiceName);
   DbgTrace(1, "-ObtainAuthTokenInt- HostName = %s\n", pHostName);
   DbgTrace(1, "-ObtainAuthTokenInt- BufferLength = %d\n", *pAuthTokenBufLen);

   // Obtain our synchronization mutex
   AcquireModuleMutex;

   // Create user synchronization mutex
   retStatus = CreateUserMutex(&hUserMutex);
   if (retStatus != CASA_STATUS_SUCCESS)
   {
      DbgTrace(0, "-ObtainAuthTokenInt- Error creating mutex for the user\n", 0);
      goto exit;
   }

   // Make sure we are fully initialized
   if (g_bInitialized == false)
   {
      retStatus = InitializeLibrary();

      if (retStatus == CASA_STATUS_SUCCESS)
      {
         g_bInitialized = true;
      }
      else
      {
         goto exit;
      }
   }

   // Release our synchronization mutex
   ReleaseModuleMutex;

   // Normalize the host name
   pNormalizedHostName = NormalizeHostName(pHostName);
   if (pNormalizedHostName)
   {
      bool setupHostEntries = true;
      char *pHostNameAnd443 = NULL;
      char *pHostNameAnd2645 = NULL;
      char *pNormalizedHostNameAnd443 = NULL;
      char *pNormalizedHostNameAnd2645 = NULL;
      ATSHostEntry serviceHostEntry443 = {{NULL, NULL}, NULL, NULL, 0};
      ATSHostEntry serviceHostEntry2645 = {{NULL, NULL}, NULL, NULL, 0};
      ATSHostEntry serviceNormalizedHostEntry443 = {{NULL, NULL}, NULL, NULL, 0};
      ATSHostEntry serviceNormalizedHostEntry2645 = {{NULL, NULL}, NULL, NULL, 0};
      LIST_ENTRY *pListEntry;
      ATSHostEntry *pHostEntryInUse;

      // Start user process synchronization
      AcquireUserMutex(hUserMutex);

      // Determine if we should setup host entries for the
      // host where the service resides.
      pListEntry = g_ATSHostList.Flink;
      while(pListEntry != &g_ATSHostList)
      {
         pHostEntryInUse = CONTAINING_RECORD(pListEntry, ATSHostEntry, listEntry);
         if (strcmp(pHostEntryInUse->pName, pHostName) == 0
             || strcmp(pHostEntryInUse->pName, pNormalizedHostName) == 0)
         {
            // The service's host is already in our list
            setupHostEntries = false;
            break;
         }

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

      // Setup host entries for the service's host if necessary
      if (setupHostEntries)
      {
         // Allocate space for the host name and port strings
         pHostNameAnd443 = malloc(strlen(pHostName) + 5);
         pHostNameAnd2645 = malloc(strlen(pHostName) + 6);
         if (pHostNameAnd443 != NULL
             && pHostNameAnd2645 != NULL)
         {
            sprintf(pHostNameAnd443, "%s:%d", pHostName, 443);
            sprintf(pHostNameAnd2645, "%s:%d", pHostName, 2645);

            serviceHostEntry2645.pNameAndPort = pHostNameAnd2645;
            serviceHostEntry2645.pName = pHostName;
            serviceHostEntry2645.port = 2645;
            InsertHeadList(&g_ATSHostList, &serviceHostEntry2645.listEntry); 

            serviceHostEntry443.pNameAndPort = pHostNameAnd443;
            serviceHostEntry443.pName = pHostName;
            serviceHostEntry443.port = 443;
            InsertHeadList(&g_ATSHostList, &serviceHostEntry443.listEntry);

            // Check if we should also setup host entries using the service's
            // normalized host name.
            if (strcmp(pHostName, pNormalizedHostName) != 0)
            {
               // The host name given and the normalized name are different. Let's
               // improve the odds by also trying to utilize the normalized name.
               pNormalizedHostNameAnd443 = malloc(strlen(pNormalizedHostName) + 5);
               pNormalizedHostNameAnd2645 = malloc(strlen(pNormalizedHostName) + 6);
               if (pNormalizedHostNameAnd443 != NULL
                   && pNormalizedHostNameAnd2645 != NULL)
               {
                  sprintf(pNormalizedHostNameAnd443, "%s:%d", pNormalizedHostName, 443);
                  sprintf(pNormalizedHostNameAnd2645, "%s:%d", pNormalizedHostName, 2645);

                  serviceNormalizedHostEntry2645.pNameAndPort = pNormalizedHostNameAnd2645;
                  serviceNormalizedHostEntry2645.pName = pNormalizedHostName;
                  serviceNormalizedHostEntry2645.port = 2645;
                  InsertHeadList(&g_ATSHostList, &serviceNormalizedHostEntry2645.listEntry); 

                  serviceNormalizedHostEntry443.pNameAndPort = pNormalizedHostNameAnd443;
                  serviceNormalizedHostEntry443.pName = pNormalizedHostName;
                  serviceNormalizedHostEntry443.port = 443;
                  InsertHeadList(&g_ATSHostList, &serviceNormalizedHostEntry443.listEntry);
               }
               else
               {
                  DbgTrace(0, "-ObtainAuthTokenInt- Buffer allocation failure\n", 0);
               }
            }
         }
         else
         {
            DbgTrace(0, "-ObtainAuthTokenInt- Buffer allocation failure\n", 0);
         }
      }

      // Now try to obtain an authentication token using the
      // host entries at our disposal.
      pListEntry = g_ATSHostList.Flink;
      while(pListEntry != &g_ATSHostList)
      {
         // Get pointer to the host entry
         pHostEntryInUse = CONTAINING_RECORD(pListEntry, ATSHostEntry, listEntry);

         // Try to find a cache entry for the service
         pCacheEntry = FindAuthTokenEntryInCache(pServiceName,
                                                 pNormalizedHostName,
                                                 pHostEntryInUse,
                                                 pCredStoreScope);
         if (pCacheEntry == NULL)
         {
            // Initialize to retry in case of failure
            int cacheEntryLifetime = DEFAULT_RETRY_LIFETIME; 
            bool advisedToRetry;
            DWORD opStartTime = GetTickCount();

            // Cache entry created, now try to obtain auth token from the CASA Server
            pToken = NULL;
            retStatus = ObtainAuthTokenFromServer(pServiceName,
                                                  pHostName,
                                                  pNormalizedHostName,
                                                  pHostEntryInUse,
                                                  pCredStoreScope,
                                                  &pToken,
                                                  &cacheEntryLifetime,
                                                  &advisedToRetry);

            // Retry if not successful and if advised to do so
            if (!CASA_SUCCESS(retStatus)
                && advisedToRetry)
            {
               retStatus = ObtainAuthTokenFromServer(pServiceName,
                                                     pHostName,
                                                     pNormalizedHostName,
                                                     pHostEntryInUse,
                                                     pCredStoreScope,
                                                     &pToken,
                                                     &cacheEntryLifetime,
                                                     &advisedToRetry);
            }

            // Try to add the entry to the cache if we did not fail due
            // to authentication failure.
            if (CasaStatusCode(retStatus) != CASA_STATUS_AUTHENTICATION_FAILURE)
            {
               DWORD opEndTime = GetTickCount();

               // We only want to cache bad results if the operation took a
               // considerable amount of time.
               if (CASA_SUCCESS(retStatus)
                   || opEndTime >= (opStartTime  + (BAD_CACHE_TRIGER_TIME * 1000)))
               {
                  pCacheEntry = CreateAuthTokenCacheEntry(pServiceName,
                                                          pNormalizedHostName,
                                                          pHostEntryInUse,
                                                          retStatus,
                                                          pToken,
                                                          cacheEntryLifetime,
                                                          pCredStoreScope);
                  if (pCacheEntry)
                  {
                     // Release the cache entry if the resulting status is not successful
                     if (!CASA_SUCCESS(retStatus))
                     {
                        FreeAuthCacheEntry(pCacheEntry);
                     }
                  }
               }
            }

            // Release authentication token if present
            if (pToken)
            {
               // Clear the memory before releasing the buffer since it contains
               // security sensitive data.
               memset(pToken, 0, strlen(pToken));
               free(pToken);
            }
         }
         else
         {
            // Cache entry found, update the return status with the information saved in it
            // and release it if its status is not successful.
            if (!CASA_SUCCESS(retStatus = pCacheEntry->status))
            {
               FreeAuthCacheEntry(pCacheEntry);
            }
         }

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

            // We have an authentication token, try to return it to the caller
            // after verifying that the supplied buffer is big enough.
            if (*pAuthTokenBufLen >= tokenLen)
            {
               // Return the auth token to the caller
               DbgTrace(2, "-ObtainAuthTokenInt- Copying the token into the callers buffer\n", 0);
               strcpy(pAuthTokenBuf, pCacheEntry->token);
            }
            else
            {
               if (*pAuthTokenBufLen != 0)
               {
                  DbgTrace(0, "-ObtainAuthTokenInt- The supplied buffer is not large enough", 0);
               }
               retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                           CASA_FACILITY_AUTHTOKEN,
                                           CASA_STATUS_BUFFER_OVERFLOW);
            }

            // Return the token length to the caller
            *pAuthTokenBufLen = tokenLen;

            FreeAuthCacheEntry(pCacheEntry);

            // No need to loop any longer
            break;
         }

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

      // Unlink the service host entries if necessary
      if (pHostNameAnd443 != NULL
          && pHostNameAnd2645 != NULL)
      {
         RemoveEntryList(&serviceHostEntry2645.listEntry);
         RemoveEntryList(&serviceHostEntry443.listEntry);

         if (pNormalizedHostNameAnd443 != NULL
             && pNormalizedHostNameAnd2645 != NULL)
         {
            RemoveEntryList(&serviceNormalizedHostEntry2645.listEntry);
            RemoveEntryList(&serviceNormalizedHostEntry443.listEntry);
         }
      }

      // Stop user process synchronization
      ReleaseUserMutex(hUserMutex);

      // Free the space allocated during processing of the request
      if (pHostNameAnd443)
         free(pHostNameAnd443);

      if (pHostNameAnd2645)
         free(pHostNameAnd2645);

      if (pNormalizedHostNameAnd443)
         free(pNormalizedHostNameAnd443);

      if (pNormalizedHostNameAnd2645)
         free(pNormalizedHostNameAnd2645);

      free(pNormalizedHostName);
   }
   else
   {
      DbgTrace(0, "-ObtainAuthTokenInt- Host name normalization failed\n", 0);
      retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                  CASA_FACILITY_AUTHTOKEN,
                                  CASA_STATUS_NAME_RESOLVE_ERROR);
   }

exit:

   if (hUserMutex != NULL)
   {
      DestroyUserMutex(hUserMutex);
   }
   DbgTrace(1, "-ObtainAuthTokenInt- End, retStatus = %08X\n", retStatus);

   return retStatus;
}


//++=======================================================================
CasaStatus SSCS_CALL
ObtainAuthToken(
   IN    const char *pServiceName,
   IN    const char *pHostName,
   INOUT char *pAuthTokenBuf,
   INOUT int *pAuthTokenBufLen)
//
//  Arguments: 
//    pServiceName -
//       Pointer to NULL terminated string that contains the
//       name of the service to which the client is trying to
//       authenticate.
//               
//    pHostName -
//       Pointer to NULL terminated string that contains the
//       name of the host where resides the service to which the
//       client is trying to authenticate. Note that the name
//       can either be a DNS name or a dotted IP address.
//               
//    pAuthTokenBuf -
//       Pointer to buffer that will receive the authentication
//       token. The length of this buffer is specified by the
//       pAuthTokenBufLen parameter. Note that the the authentication
//       token will be in the form of a NULL terminated string.
//
//    pAuthTokenBufLen -
//       Pointer to integer that contains the length of the
//       buffer pointed at by pAuthTokenBuf. Upon return of the
//       function, the integer will contain the actual length
//       of the authentication token if the function successfully
//       completes or the buffer length required if the function
//       fails because the buffer pointed at by pAuthTokenBuf is
//       not large enough.
//   
// Returns:
//    Casa Status
//                           
// Description:
//    Get authentication token to authenticate user to specified
//    service at host.
//
// L2
//=======================================================================--
{
   CasaStatus        retStatus;

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

   // Call our internal worker
   retStatus = ObtainAuthTokenInt(pServiceName,
                                  pHostName,
                                  NULL,
                                  pAuthTokenBuf,
                                  pAuthTokenBufLen);

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

   return retStatus;
}


//++=======================================================================
void
CleanUpAuthTokenCacheInt(
   IN    const void *pCredStoreScope)
//
//  Arguments:
//    pCredStoreScope -
//       Pointer to CASA structure for scoping credential store access
//       to specific users. This can only be leveraged by applications
//       running in the context of System.
//   
// Returns:
//    Nothing
//                           
// Description:
//    Flush the AuthToken cache.
//=======================================================================--
{
   CasaStatus  retStatus;
   HANDLE      hUserMutex = NULL;

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

   // Obtain our synchronization mutex
   AcquireModuleMutex;

   // Create user synchronization mutex
   retStatus = CreateUserMutex(&hUserMutex);
   if (retStatus != CASA_STATUS_SUCCESS)
   {
      DbgTrace(0, "-CleanUpAuthTokenCacheInt- Error creating mutex for the user\n", 0);
      goto exit;
   }

   // Make sure we are fully initialized
   if (g_bInitialized == false)
   {
     retStatus = InitializeLibrary();

     if (retStatus == CASA_STATUS_SUCCESS)
     {
       g_bInitialized = true;
     }
     else
     {
       goto exit;
     }
   }

   // Release our synchronization mutex
   ReleaseModuleMutex;

   // Start user process synchronization
   AcquireUserMutex(hUserMutex);

   // Delete all of the tokens in our cache
   DeleteAuthTokenEntriesInCache(pCredStoreScope);
   DeleteSessionTokenEntriesInCache(pCredStoreScope);

   // Stop user process synchronization
   ReleaseUserMutex(hUserMutex);

exit:

   if (hUserMutex != NULL)
   {
      DestroyUserMutex(hUserMutex);
   }

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


//++=======================================================================
void SSCS_CALL
CleanUpAuthTokenCache(void)
//
//  Arguments: None. 
//   
// Returns:
//    Nothing
//                           
// Description:
//    Flush the AuthToken cache.
//=======================================================================--
{
   DbgTrace(1, "-CleanUpAuthTokenCache- Start\n", 0);

   // Call our internal worker
   CleanUpAuthTokenCacheInt(NULL);

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


//++=======================================================================
void
CreateATSHostEntry(
   IN const char *pHostName,
   IN uint16_t port)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   ATSHostEntry   *pHostEntry;

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

   // Create host entry
   pHostEntry = malloc(sizeof(ATSHostEntry));
   if (pHostEntry != NULL)
   {
      // Allocate buffers to keep copies of the strings provided
      pHostEntry->pNameAndPort = malloc(strlen(pHostName) + 7);
      pHostEntry->pName = malloc(strlen(pHostName) + 1);
      if (pHostEntry->pNameAndPort != NULL
          && pHostEntry->pName != NULL)
      {
         // Setup the strings into the corresponding buffers
         sprintf(pHostEntry->pNameAndPort, "%s:%d", pHostName, port);
         strcpy(pHostEntry->pName, pHostName);

         // Save host port in entry
         pHostEntry->port = port;

         // Insert the entry at the tail of the list
         InsertTailList(&g_ATSHostList, &pHostEntry->listEntry);
      }
      else
      {
         DbgTrace(0, "-CreateATSHostEntry- Failed to allocate buffer\n", 0);
         if (pHostEntry->pNameAndPort)
            free(pHostEntry->pNameAndPort);
         if (pHostEntry->pName)
            free(pHostEntry->pName);
         free(pHostEntry);
      }
   }
   else
   {
      DbgTrace(0, "-CreateATSHostEntry- Failed to allocate buffer for host entry\n", 0);
   }

   DbgTrace(1, "-CreateATSHostEntry- Exit\n", 0);
}


//++=======================================================================
int
InitializeLibrary(void)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   int         retStatus = -1;
   int         getConfigStatus = -1;
   ConfigIf    *pClientConfigIf;
   char        *pDebugLevelSetting;
   char        *pDebugLogFolderPathSetting;
   char        *pATSHostListSetting;
   char        *pDisableSecureConnections;
   char        *pAllowInvalidCerts;
   char        *pUsersCannotAllowInvalidCerts;

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

   // Initialize the ATSHostList
   InitializeListHead(&g_ATSHostList);

   // Try to obtain client configuration settings
   getConfigStatus = GetConfigInterface(clientConfigFolder,
                                        "client",
                                        &pClientConfigIf);
   if (CASA_SUCCESS(getConfigStatus)
       && CasaStatusCode(getConfigStatus) != CASA_STATUS_OBJECT_NOT_FOUND)
   {
      // Check if a DebugLevel has been configured
      pDebugLevelSetting = pClientConfigIf->getEntryValue(pClientConfigIf, "DebugLevel");
      if (pDebugLevelSetting != NULL)
      {
         DbgTrace(0, "-InitializeLibrary- DebugLevel configured = %s\n", pDebugLevelSetting);
         
         // Convert the number to hex
         DebugLevel = (int) dtoul(pDebugLevelSetting, strlen(pDebugLevelSetting));

         // Free the buffer holding the debug level
         pClientConfigIf->freeValueString(pClientConfigIf, pDebugLevelSetting);
      }

      // Check if a DebugLogFolderPath has been configured
      pDebugLogFolderPathSetting = pClientConfigIf->getEntryValue(pClientConfigIf, "DebugLogFolderPath");
      if (pDebugLogFolderPathSetting != NULL)
      {
         DbgTrace(0, "-InitializeLibrary- DebugLogFolderPath configured = %s\n", pDebugLogFolderPathSetting);

         // Use the setting to come up with the path to the debug log file
         g_pDebugLogFilePath = malloc(strlen(LOG_FILE_NAME) + strlen(pDebugLogFolderPathSetting) + 1);
         if (g_pDebugLogFilePath)
         {
            strcpy(g_pDebugLogFilePath, pDebugLogFolderPathSetting);
            strcat(g_pDebugLogFilePath, LOG_FILE_NAME);
         }
         else
         {
            DbgTrace(0, "-InitializeLibrary- Failed to allocate buffer for debug file path\n", 0);
         }

         // Free the buffer holding the debug folder path
         pClientConfigIf->freeValueString(pClientConfigIf, pDebugLogFolderPathSetting);
      }

      // Check if an ATS Host List has been configured
      pATSHostListSetting = pClientConfigIf->getEntryValue(pClientConfigIf, "ATSHostList");
      if (pATSHostListSetting != NULL)
      {
         char *pSavePtr;
         char *pHostAndPort;

         DbgTrace(0, "-InitializeLibrary- ATSHostList configured = %s\n", pATSHostListSetting);

         // Go through all configured host addresses
         pHostAndPort = strtok_r(pATSHostListSetting, ";", &pSavePtr);
         while (pHostAndPort != NULL)
         {
            char *pSavePtr2;
            char *pHostName;

            // Check if the host address includes the listen port number.
            pHostName = strtok_r(pHostAndPort, ":", &pSavePtr2);
            if (pHostName != NULL)
            {
               uint16_t port = 0;
               char *pHostPort = strtok_r(NULL, ":", &pSavePtr2);
               if (pHostPort != NULL)
               {
                  // Convert the number to hex
                  port = (uint16_t) dtoul(pHostPort, strlen(pHostPort));
               }

               // Now create the necessary ATS Host entries
               if (port == 0)
               {
                  // The port number was not configured, create an ATS Host entry
                  // for each possible listen port.
                  CreateATSHostEntry(pHostName, 443);
                  CreateATSHostEntry(pHostName, 2645);
               }
               else
               {
                  // Create ATS Host entry for configured port
                  CreateATSHostEntry(pHostName, port);
               }
            }
            else
            {
               DbgTrace(0, "-InitializeLibrary- Error parsing configured host address\n", 0);
            }

            // Advance to the next entry
            pHostAndPort = strtok_r(NULL, ";", &pSavePtr);
         }

         // Free the buffer holding the ats host list setting
         pClientConfigIf->freeValueString(pClientConfigIf, pATSHostListSetting);
      }

      // Check if the DisableSecureConnections setting has been configured
      pDisableSecureConnections = pClientConfigIf->getEntryValue(pClientConfigIf, "DisableSecureConnections");
      if (pDisableSecureConnections != NULL)
      {
         DbgTrace(0, "-InitializeLibrary- DisableSecureConnections setting configured = %s\n", pDisableSecureConnections);

         // Adjust the g_rpcFlags variable based on the setting
         if (stricmp(pDisableSecureConnections, "true") == 0)
         {
            g_rpcFlags &= ~SECURE_RPC_FLAG;
         }
         else if (stricmp(pDisableSecureConnections, "false") == 0)
         {
            g_rpcFlags |= SECURE_RPC_FLAG;
         }

         // Free the buffer holding the DisableSecureConnections setting
         pClientConfigIf->freeValueString(pClientConfigIf, pDisableSecureConnections);
      }

      // Check the AllowUntrustedCerts setting if using secure connections
      if (g_rpcFlags & SECURE_RPC_FLAG)
      {
         // Check if the AllowUntrustedCerts setting has been configured
         pAllowInvalidCerts = pClientConfigIf->getEntryValue(pClientConfigIf, "AllowUntrustedCerts");
         if (pAllowInvalidCerts != NULL)
         {
            DbgTrace(0, "-InitializeLibrary- AllowUntrustedCerts setting configured = %s\n", pAllowInvalidCerts);

            // Adjust the g_rpcFlags variable based on the setting
            if (stricmp(pAllowInvalidCerts, "false") == 0)
            {
               g_rpcFlags &= ~ALLOW_INVALID_CERTS_RPC_FLAG;
            }
            else if (stricmp(pAllowInvalidCerts, "true") == 0)
            {
               g_rpcFlags |= ALLOW_INVALID_CERTS_RPC_FLAG;
            }

            // Free the buffer holding the AllowInvalidCerts setting
            pClientConfigIf->freeValueString(pClientConfigIf, pAllowInvalidCerts);
         }

         // Check the UsersCannotAllowInvalidCerts setting if not allowing invalid certs.
         if ((g_rpcFlags & ALLOW_INVALID_CERTS_RPC_FLAG) == 0)
         {
            // Check if the UsersCannotAllowInvalidCerts setting has been configured
            pUsersCannotAllowInvalidCerts = pClientConfigIf->getEntryValue(pClientConfigIf, "UsersCannotAllowInvalidCerts");
            if (pUsersCannotAllowInvalidCerts != NULL)
            {
               DbgTrace(0, "-InitializeLibrary- UsersCannotAllowInvalidCerts setting configured = %s\n", pUsersCannotAllowInvalidCerts);

               // Adjust the g_rpcFlags variable based on the setting
               if (stricmp(pUsersCannotAllowInvalidCerts, "false") == 0)
               {
                  g_rpcFlags |= ALLOW_INVALID_CERTS_USER_APPROVAL_RPC_FLAG;
               }
               else if (stricmp(pUsersCannotAllowInvalidCerts, "true") == 0)
               {
                  g_rpcFlags &= ~ALLOW_INVALID_CERTS_USER_APPROVAL_RPC_FLAG;
               }

               // Free the buffer holding the UsersCannotAllowInvalidCerts setting
               pClientConfigIf->freeValueString(pClientConfigIf, pUsersCannotAllowInvalidCerts);
            }
         }
      }

      // Release config interface instance
      pClientConfigIf->releaseReference(pClientConfigIf);
   }

   // Initialize the host name normalization
   retStatus = InitializeHostNameNormalization();
   if (CASA_SUCCESS(retStatus))
   {
      // Initialize the auth cache
      retStatus = InitializeAuthCache();
      if (CASA_SUCCESS(retStatus))
      {
         retStatus = InitializeRpc();
      }
      else
      {
         DbgTrace(0, "-InitializeLibrary- Auth cache intialization failed\n", 0);
      }
   }
   else
   {
         DbgTrace(0, "-InitializeLibrary- HostName Normalizer intialization failed\n", 0);
   }

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

   return retStatus;
}


//++=======================================================================
void
UnInitializeLibrary(void)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   LIST_ENTRY     *pListEntry;
   ATSHostEntry   *pHostEntry;

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

   // Un-initialize the host name normalization
   UnInitializeHostNameNormalization();

   // Un-initialize the auth cache
   UnInitializeAuthCache();

   // Un-initialize the Rpc engine
   UnInitializeRpc();

   // Free necessary buffers
   if (g_pDebugLogFilePath)
   {
      char *pBuffer = g_pDebugLogFilePath;
      g_pDebugLogFilePath = NULL;
      free(pBuffer);
   }

   pListEntry = g_ATSHostList.Flink;
   if (pListEntry)
   {
      while (pListEntry != &g_ATSHostList)
      {
         pHostEntry = CONTAINING_RECORD(pListEntry, ATSHostEntry, listEntry);
         RemoveEntryList(pListEntry);
         free(pHostEntry->pNameAndPort);
         free(pHostEntry->pName);
         free(pHostEntry);
         pListEntry = g_ATSHostList.Flink;
      }
   }

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


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