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

#ifndef CASA_STATUS_NAME_RESOLVE_ERROR
#define CASA_STATUS_NAME_RESOLVE_ERROR         ((CasaStatus)0x00000024)
#endif

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

int
InitializeLibrary(void);

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

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

//
// Operating parameter
// 
bool        g_bInitialized = false;
long        g_rpcFlags = SECURE_RPC_FLAG | ALLOW_INVALID_CERTS_USER_APPROVAL_RPC_FLAG;
char        *g_pATSHostName = NULL;
uint16_t    g_ATSPort = 2645;


//++=======================================================================
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;

   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;
         int   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 successful or 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,
   INOUT char **ppAuthToken,
   INOUT int *pTokenLifetime,
   IN    void *pCredStoreScope)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   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((g_pATSHostName != NULL) ? g_pATSHostName : pHostName,
                                g_ATSPort);
   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, "localhost"); // tbd - This will be changed in the future so that we can support services residing in a different host than the ATS
      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,
                                                    (g_pATSHostName != NULL) ? g_pATSHostName : pHostName,
                                                    pCredStoreScope,
                                                    &pSessionToken,
                                                    &pSessionTokenAuthContext);
                     if (CASA_SUCCESS(retStatus))
                     {
                        // Request auth token for the service
                        free(pReqMsg);
                        pReqMsg = BuildGetAuthTokenMsg(pServiceName, "localhost", pSessionToken); // tbd - This will be changed in the future so that we can support services residing in a different host than the ATS
                        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);
                                    }
                                 }
                              }
                              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,
   INOUT char *pAuthTokenBuf,
   INOUT int *pAuthTokenBufLen,
   IN    void *pCredStoreScope)
//
//  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.
//   
//    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:
//    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)
   {
      // Start user process synchronization
      AcquireUserMutex(hUserMutex);

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

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

         // 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 = CreateAuthTokenCacheEntry(pServiceName,
                                                    pNormalizedHostName,
                                                    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);
      }

      // Stop user process synchronization
      ReleaseUserMutex(hUserMutex);

      // Free the space allocated for the normalized host name
      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,
                                  pAuthTokenBuf,
                                  pAuthTokenBufLen,
                                  NULL);

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

   return retStatus;
}


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

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

   // 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
         free(pDebugLevelSetting);
      }

      // Check if an ATS hostname has been configured
      g_pATSHostName = pClientConfigIf->getEntryValue(pClientConfigIf, "ATS-hostname");
      if (g_pATSHostName != NULL)
      {
         DbgTrace(0, "-InitializeLibrary- ATS hostname configured = %s\n", g_pATSHostName);
      }

      // 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
         free(pDisableSecureConnections);
      }

      // Check the AllowInvalidCerts setting if using secure connections
      if (g_rpcFlags & SECURE_RPC_FLAG)
      {
         // Check if the AllowInvalidCerts setting has been configured
         pAllowInvalidCerts = pClientConfigIf->getEntryValue(pClientConfigIf, "AllowInvalidCerts");
         if (pAllowInvalidCerts != NULL)
         {
            DbgTrace(0, "-InitializeLibrary- AllowInvalidCerts 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
            free(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
               free(pUsersCannotAllowInvalidCerts);
            }
         }
      }

      // Check if an ATS port number has been configured
      pATSPortSetting = pClientConfigIf->getEntryValue(pClientConfigIf, "ATS-port");
      if (pATSPortSetting != NULL)
      {
         DbgTrace(0, "-InitializeLibrary- ATS port number configured = %s\n", pATSPortSetting);
         
         // Convert the number to hex
         g_ATSPort = (int) dtoul(pATSPortSetting, strlen(pATSPortSetting));

         // Free the buffer holding the port number
         free(pATSPortSetting);
      }

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

   // Initialize the host name normalization
   retStatus = InitializeHostNameNormalization();
   if (CASA_SUCCESS(retStatus))
   {
      // Normalize ATS host name if configured
      if (g_pATSHostName)
      {
         char *pNormalizedHostName = NormalizeHostName(g_pATSHostName);
         if (pNormalizedHostName)
         {
            // Use this name instead of the one that we already have
            free(g_pATSHostName);
            g_pATSHostName = pNormalizedHostName;
         }
         else
         {
            DbgTrace(0, "-InitializeLibrary- ATS Hostname normalization failed\n", 0);
         }
      }

      // 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
//=======================================================================--
{
   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();

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


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