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

//
// Normalized Host Name Cache Entry definition
//
typedef struct _NormalizedHostNameCacheEntry
{
   LIST_ENTRY  listEntry;
   char        *pHostName;
   char        *pNormalizedHostName;
   size_t      buffLengthRequired;
    
} NormalizedHostNameCacheEntry, *PNormalizedHostNameCacheEntry;


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

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

static
BOOLEAN  hostNameNormalizationInitialized = FALSE;

// Normalized host name cache list head
static
LIST_ENTRY  normalizedHostNameCacheListHead;

// Synchronization mutex for the normalized host name cache
static
HANDLE   hNormalizedHostNameCacheMutex;

// Client configuration file folder
char  clientConfigFolderPartialPath[] = "Novell\\Casa\\Etc\\Auth";
char  clientConfigFolder[MAX_PATH + sizeof(clientConfigFolderPartialPath)];

// Authentication mechanism configuration file folder
char  mechConfigFolderPartialPath[] = "Novell\\Casa\\Etc\\Auth\\Mechanisms";
char  mechConfigFolder[MAX_PATH + sizeof(mechConfigFolderPartialPath)];

// Program files folder
char  programFilesFolder[MAX_PATH] = {0};

// Path separator
char  pathCharString[] = "\\";


//++=======================================================================
CasaStatus
CreateUserMutex(
   HANDLE *phMutex
   )
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
#define USER_MUTEX_NAME_FMT_STRING "Global\\CASA_Auth_Mutex_%s"

   CasaStatus  retStatus = CASA_STATUS_SUCCESS;
   char        *pUsername = NULL;
   DWORD       nameLength = 0;

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

   // Get the size of the buffer required to obtain the user name
   GetUserName(pUsername, &nameLength);
   if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
   {
      // Allocate buffer to hold the user name
      pUsername = (char*) malloc(nameLength);
      if (pUsername)
      {
         // Get the name of the user
         if (GetUserName(pUsername, &nameLength))
         {
            SECURITY_ATTRIBUTES  mutexAttributes;
            char                 *pMutexName;

            // Allocate a buffer to hold the mutex name
            pMutexName = (char*) malloc(sizeof(USER_MUTEX_NAME_FMT_STRING) + nameLength);
            if (pMutexName)
            {
               // Now lets create a global semaphore for the
               // user and allow its handle to be inherited.
               mutexAttributes.nLength = sizeof(mutexAttributes);
               mutexAttributes.lpSecurityDescriptor = NULL;
               mutexAttributes.bInheritHandle = TRUE;
               if (sprintf(pMutexName, USER_MUTEX_NAME_FMT_STRING, pUsername) != -1)
               {
                  *phMutex = CreateMutex(&mutexAttributes,
                                         FALSE,
                                         pMutexName);
                  if (*phMutex == NULL)
                  {
                     DbgTrace(0, "-CreateUserMutex- CreateMutex failed, error = %d\n", GetLastError());
                     retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                                 CASA_FACILITY_AUTHTOKEN,
                                                 CASA_STATUS_UNSUCCESSFUL);
                  }
               }
               else
               {
                  DbgTrace(0, "-CreateUserMutex- sprintf failed, error = %d\n", GetLastError());
                  retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                              CASA_FACILITY_AUTHTOKEN,
                                              CASA_STATUS_UNSUCCESSFUL);
               }

               // Free the buffer used to hold the user mutex name
               free(pMutexName);
            }
            else
            {
               DbgTrace(0, "-CreateUserMutex- Buffer allocation failure\n", 0);
               retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                           CASA_FACILITY_AUTHTOKEN,
                                           CASA_STATUS_INSUFFICIENT_RESOURCES);
            }
         }
         else
         {
            DbgTrace(0, "-CreateUserMutex- GetUserName failed, error = %d\n", GetLastError());
            retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                        CASA_FACILITY_AUTHTOKEN,
                                        CASA_STATUS_UNSUCCESSFUL);
         }

         // Free the buffer allocated to hold the user name
         free(pUsername);
      }
      else
      {
         DbgTrace(0, "-CreateUserMutex- Buffer allocation error\n", 0);
         retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                     CASA_FACILITY_AUTHTOKEN,
                                     CASA_STATUS_INSUFFICIENT_RESOURCES);
      }
   }
   else
   {
      DbgTrace(0, "-CreateUserMutex- Unexpected GetUserName error, error = %d\n", GetLastError());
      retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                  CASA_FACILITY_AUTHTOKEN,
                                  CASA_STATUS_UNSUCCESSFUL);
   }

   DbgTrace(1, "-CreateUserMutex- End, retStatus\n", retStatus);

   return retStatus;
}


//++=======================================================================
void
AcquireUserMutex(
   HANDLE hMutex
   )
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   DbgTrace(2, "-AcquireUserMutex- Start\n", 0);

   WaitForSingleObject(hMutex, INFINITE);

   DbgTrace(2, "-AcquireUserMutex- End\n", 0);
}


//++=======================================================================
void
ReleaseUserMutex(
   HANDLE hMutex
   )
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   DbgTrace(2, "-ReleaseUserMutex- Start\n", 0);

   if (ReleaseMutex(hMutex) == 0)
   {
      DbgTrace(0, "-ReleaseUserMutex- ReleaseMutex failed, error = %d\n", GetLastError());
   }

   DbgTrace(2, "-ReleaseUserMutex- End\n", 0);
}


//++=======================================================================
void
DestroyUserMutex(
   HANDLE hMutex
   )
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   DbgTrace(2, "-DestroyUserMutex- Start\n", 0);

   if (CloseHandle(hMutex) == 0)
   {
      DbgTrace(0, "-DestroyUserMutex- CloseHandle failed, error = %d\n", GetLastError());
   }

   DbgTrace(2, "-DestroyUserMutex- End\n", 0);
}


//++=======================================================================
LIB_HANDLE
OpenLibrary(
   IN    char *pFileName)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   LIB_HANDLE  libHandle = NULL;
   char        *pLibPath = NULL;

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

   // Check for a partial path to the program files folder
   if (strlen(pFileName) > strlen("\\Program Files"))
   {
      if (_strnicmp(pFileName, "\\Program Files", strlen("\\Program Files")) == 0)
      {
         // The file name contains a partial path to the program files folder,
         // convert it to an absolute path.
         char *p = pFileName + strlen("\\Program Files");
         pLibPath = malloc(strlen(programFilesFolder) + strlen(p) + 1);
         if (pLibPath)
         {
            strcpy(pLibPath, programFilesFolder);
            strcat(pLibPath, p);
         }
         else
         {
            DbgTrace(0, "-OpenLibrary- Buffer allocation failure\n", 0);
         }
      }
      else
      {
         // Use the path specified
         pLibPath = pFileName;
      }
   }
   else
   {
      // Use the path specified
      pLibPath = pFileName;
   }

   // Proceed if pLibPath has been setup
   if (pLibPath)
   {
      libHandle = LoadLibrary(pLibPath);
      if (libHandle == NULL)
      {
         DbgTrace(0, "-OpenLibrary- Not able to load library, error = %d\n", GetLastError());
      }

      // Free memory allocated for library path if necessary
      if (pLibPath != pFileName)
         free(pLibPath);
   }

   DbgTrace(1, "-OpenLibrary- End, handle = %08X\n", libHandle);

   return libHandle;
}


//++=======================================================================
void
CloseLibrary(
   IN    LIB_HANDLE libHandle)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   DbgTrace(1, "-CloseLibrary- Start\n", 0);

   FreeLibrary(libHandle);

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


//++=======================================================================
void*
GetFunctionPtr(
   IN    LIB_HANDLE libHandle,
   IN    char *pFunctionName)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   void  *pFuncPtr;

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

   pFuncPtr = GetProcAddress(libHandle, pFunctionName);
   if (pFuncPtr == NULL)
   {
      DbgTrace(0, "-GetFunctionPtr- Not able to obtain func ptr, error = %d\n", GetLastError());
   }

   DbgTrace(1, "-GetFunctionPtr- End, pFuncPtr = %08X\n", pFuncPtr);

   return pFuncPtr;
}


//++=======================================================================
char*
NormalizeHostName(
   IN    const char *pHostName)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   char                          *pNormalizedName = NULL;
   LIST_ENTRY                    *pListEntry;
   NormalizedHostNameCacheEntry  *pEntry = NULL;

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

   // Obtain our synchronization mutex
   WaitForSingleObject(hNormalizedHostNameCacheMutex, INFINITE);

   // First try to find an entry in the normalized host name cache
   // for the host name provided.
   pListEntry = normalizedHostNameCacheListHead.Flink;
   while (pListEntry != &normalizedHostNameCacheListHead)
   {
      // Get pointer to the entry
      pEntry = CONTAINING_RECORD(pListEntry, NormalizedHostNameCacheEntry, listEntry);

      // Check if the entry is for the host name
      if (strcmp(pHostName, pEntry->pHostName) == 0)
      {
         // This entry corresponds to the given host name
         break;
      }
      else
      {
         // The entry does not correspond to the given host name
         pEntry = NULL;
      }

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

   // Check if we found an entry in our cache for the given host name
   if (pEntry)
   {
      // Entry found, obtain the normalized name from it.
      pNormalizedName = (char*) malloc(pEntry->buffLengthRequired);
      if (pNormalizedName)
      {
         // Copy the normalized name onto the allocated buffer
         strcpy(pNormalizedName, pEntry->pNormalizedHostName);
      }
      else
      {
         DbgTrace(0, "-NormalizeHostName- Buffer allocation error\n", 0);
      }
   }
   else
   {
      // An entry was not found in our cache, create one.
      pEntry = (NormalizedHostNameCacheEntry*) malloc(sizeof(NormalizedHostNameCacheEntry));
      if (pEntry)
      {
         // Zero the entry
         memset(pEntry, 0, sizeof(*pEntry));

         // Allocate a buffer to hold the host name in the entry
         pEntry->pHostName = (char*) malloc(strlen(pHostName) + 1);
         if (pEntry->pHostName)
         {
            struct hostent       *pLookupResult;
            struct sockaddr_in   sockAddr = {0};

            // Copy the host name given into the allocated buffer
            strcpy(pEntry->pHostName, pHostName);

            // Now try to resolve the normalized name
            pLookupResult = gethostbyname(pHostName);
            if (pLookupResult
                && pLookupResult->h_addrtype == AF_INET
                && pLookupResult->h_length > 0
                && pLookupResult->h_addr_list[0] != NULL)
            {
               char *pDnsHostName = (char*) malloc(NI_MAXHOST + 1);
               if (pDnsHostName)
               {
                  // Set up a sockaddr structure
                  sockAddr.sin_family = AF_INET;
                  sockAddr.sin_addr.S_un.S_addr = *((int*) pLookupResult->h_addr_list[0]);

                  // Now try to resolve the name using DNS
                  if (getnameinfo((const struct sockaddr*) &sockAddr,
                                  sizeof(sockAddr),
                                  pDnsHostName,
                                  NI_MAXHOST,
                                  NULL,
                                  0,
                                  NI_NAMEREQD) == 0)
                  {
                     // We resolved the address to a DNS name, use it as the normalized name.
                     pEntry->buffLengthRequired = strlen(pDnsHostName) + 1;
                     pEntry->pNormalizedHostName = (char*) malloc(pEntry->buffLengthRequired);
                     if (pEntry->pNormalizedHostName)
                     {
                        // Copy the dns name
                        strcpy(pEntry->pNormalizedHostName, pDnsHostName);
                     }
                     else
                     {
                        DbgTrace(0, "-NormalizeHostName- Buffer allocation error\n", 0);
                     }
                  }
                  else
                  {
                     DbgTrace(0, "-NormalizeHostName- getnameInfo failed, error %d\n", WSAGetLastError());

                     // Not able to resolve the name in DNS, just use the host name as
                     // the normalized name.
                     pEntry->buffLengthRequired = strlen(pHostName) + 1;
                     pEntry->pNormalizedHostName = (char*) malloc(pEntry->buffLengthRequired);
                     if (pEntry->pNormalizedHostName)
                     {
                        // Copy the host name
                        strcpy(pEntry->pNormalizedHostName, pHostName);
                     }
                     else
                     {
                        DbgTrace(0, "-NormalizeHostName- Buffer allocation error\n", 0);
                     }
                  }

                  // Free the buffer allocated to hold the DNS name
                  free(pDnsHostName);
               }
               else
               {
                  DbgTrace(0, "-NormalizeHostName- Buffer allocation failure\n", 0);
               }
            }
            else
            {
               DbgTrace(0, "-NormalizeHostName- Name resolution failed, error = %d\n", WSAGetLastError());
            }
         }
         else
         {
            DbgTrace(0, "-NormalizeHostName- Buffer allocation error\n", 0);

            // Free the space allocated for the entry
            free(pEntry);
         }

         // Proceed based on whether or not we normalized the name
         if (pEntry->pNormalizedHostName)
         {
            // The name was normalized, save the entry in our cache.
            InsertHeadList(&normalizedHostNameCacheListHead, &pEntry->listEntry);

            // Return the normalized name present in the entry
            pNormalizedName = (char*) malloc(pEntry->buffLengthRequired);
            if (pNormalizedName)
            {
               // Copy the normalized name onto the allocated buffer
               strcpy(pNormalizedName, pEntry->pNormalizedHostName);
            }
            else
            {
               DbgTrace(0, "-NormalizeHostName- Buffer allocation error\n", 0);
            }
         }
         else
         {
            // The host name was not normalized, free allocated resources.
            if (pEntry->pHostName)
               free(pEntry->pHostName);
            free(pEntry);
         }
      }
      else
      {
         DbgTrace(0, "-NormalizeHostName- Buffer allocation error\n", 0);
      }
   }

   // Release our synchronization mutex
   if (ReleaseMutex(hNormalizedHostNameCacheMutex) == 0)
   {
      DbgTrace(0, "-NormalizeHostName- ReleaseMutex failed, error\n", 0);
   }

   DbgTrace(1, "-NormalizeHostName- End, pNormalizedName = %08X\n", pNormalizedName);

   return pNormalizedName;
}


//++=======================================================================
CasaStatus
InitializeHostNameNormalization(void)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   CasaStatus  retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                           CASA_FACILITY_AUTHTOKEN,
                                           CASA_STATUS_UNSUCCESSFUL);
   int         winsockStartupResult;
   WSADATA     winsockData;

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

   // Initialize winsock
   if ((winsockStartupResult = WSAStartup(MAKEWORD(2,2), &winsockData)) == 0)
   {
      // Initialize the cache list head
      InitializeListHead(&normalizedHostNameCacheListHead);

      // Create a cache mutex only applicable to the current process
      hNormalizedHostNameCacheMutex = CreateMutex(NULL,
                                                  FALSE,
                                                  NULL);
      if (hNormalizedHostNameCacheMutex != NULL)
      {
         hostNameNormalizationInitialized = TRUE;
         retStatus = CASA_STATUS_SUCCESS;
      }
      else
      {
         DbgTrace(0, "-InitializeHostNameNormalization- CreateMutex failed, error = %d\n", GetLastError());
         WSACleanup();
      }
   }
   else
   {
      DbgTrace(0, "-InitializeHostNameNormalization- WSAStartup failed, error = %d\n", winsockStartupResult);
   }

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

   return retStatus;
}


//++=======================================================================
void
UnInitializeHostNameNormalization(void)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   LIST_ENTRY                    *pListEntry;
   NormalizedHostNameCacheEntry  *pEntry = NULL;

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

   // Proceed if initialization succeeded
   if (hostNameNormalizationInitialized)
   {
      // Un-initialize winsock
      WSACleanup();

      // Free up any normalized host names in our cache
      pListEntry = normalizedHostNameCacheListHead.Flink;
      while (pListEntry != &normalizedHostNameCacheListHead)
      {
         // Get pointer to the entry
         pEntry = CONTAINING_RECORD(pListEntry, NormalizedHostNameCacheEntry, listEntry);

         // Remove the entry from the list
         RemoveEntryList(pListEntry);

         // Free the entry
         if (pEntry->pHostName)
            free(pEntry->pHostName);

         if (pEntry->pNormalizedHostName)
            free(pEntry->pNormalizedHostName);

         free(pEntry);

         // Try to go to the next entry
         pListEntry = normalizedHostNameCacheListHead.Flink;
      }

      // Forget about being initialized
      hostNameNormalizationInitialized = FALSE;
   }

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


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