/***********************************************************************
 * 
 *  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;
   int         buffLengthRequired;
    
} NormalizedHostNameCacheEntry, *PNormalizedHostNameCacheEntry;


//===[ Type definitions for Local_sem ]====================================

// 
//  Notes: Most of the code for this definitions and the Local_sem_xxxx
//         functions was copied with minor modifications from W. Richard
//         Stevens book: UNIX Network Programming, Interprocess
//         Communications (Printed in 1999).
// 
//         You may ask, why not just use Posix Named Semaphores? The answer
//         is that I wish that I could but I can not tolerate that they are
//         not released when the process that holds them terminates abnormally.
// 

union semun /* define union for semctl() */
{           
  int              val;
  struct semid_ds *buf;
  unsigned short  *array;
};

typedef struct
{
  int sem_semid;     /* the System V semaphore ID */
  int sem_magic;     /* magic number if open */

} Local_sem_t;

#ifndef SEM_R
#define SEM_R 0400
#endif
     
#ifndef SEM_A
#define SEM_A 0200
#endif

#define SVSEM_MODE  (SEM_R | SEM_A | SEM_R>>3 | SEM_R>>6)

#define SEM_MAGIC   0x45678923

#define SEM_FAILED  ((Local_sem_t *)(-1)) /* avoid compiler warnings */

#ifndef SEMVMX
#define SEMVMX   32767    /* historical System V max value for sem */
#endif

#define MAX_OPEN_SEM_TRIES   10 /* for waiting for initialization */


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

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

// Normalized host name cache variables
static
LIST_ENTRY  normalizedHostNameCacheListHead;

static
pthread_mutex_t   g_hNormalizedHostNameCacheMutex = PTHREAD_MUTEX_INITIALIZER;

// Client configuration file folder
char  clientConfigFolder[] = "/etc/CASA/authtoken/client";

// Authentication mechanism configuration file folder
char  mechConfigFolder[] = "/etc/CASA/authtoken/client/mechanisms";

// Module Synchronization mutex
pthread_mutex_t   g_hModuleMutex = PTHREAD_MUTEX_INITIALIZER;

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

// Milliseconds per System Tick
static
long  g_milliSecondsPerTicks = 0;

// Named Semaphore for user variables
static
char     g_userNamedSemName[256];

static
Local_sem_t *g_userNamedSem = SEM_FAILED;

static
bool     g_userNamedSemAcquired = false;


//++=======================================================================
Local_sem_t*
Local_sem_open(const char *pathname, int oflag, ... )
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes: Most of the code for this routine was copied with minor
//         modifications from W. Richard Stevens book: UNIX Network
//         Programming, Interprocess Communications (Printed in 1999).
//
// L2
//=======================================================================--
{
   int               i, fd, semflag, semid, save_errno;
   key_t             key;
   mode_t            mode;
   va_list           ap;
   Local_sem_t       *sem;
   union semun       arg;
   unsigned int      value;
   struct semid_ds   seminfo;
   struct sembuf     initop;

   /* 4no mode for sem_open() w/out O_CREAT; guess */
   semflag = SVSEM_MODE;
   semid = -1;

   if (oflag & O_CREAT) {
      va_start(ap, oflag);    /* init ap to final named argument */
      mode = va_arg(ap, mode_t);
      value = va_arg(ap, unsigned int);
      va_end(ap);

      /* 4convert to key that will identify System V semaphore */
      if ( (fd = open(pathname, oflag, mode)) == -1)
         return(SEM_FAILED);
      close(fd);
      if ( (key = ftok(pathname, 0)) == (key_t) -1)
         return(SEM_FAILED);

      semflag = IPC_CREAT | (mode & 0777);
      if (oflag & O_EXCL)
         semflag |= IPC_EXCL;

      /* 4create the System V semaphore with IPC_EXCL */
      if ( (semid = semget(key, 1, semflag | IPC_EXCL)) >= 0) {
         /* 4success, we're the first so initialize to 0 */
         arg.val = 0;
         if (semctl(semid, 0, SETVAL, arg) == -1)
            goto err;
         /* 4then increment by value to set sem_otime nonzero */
         if (value > SEMVMX) {
            errno = EINVAL;
            goto err;
         }
         initop.sem_num = 0;
         initop.sem_op  = value;
         initop.sem_flg = 0;
         if (semop(semid, &initop, 1) == -1)
            goto err;
         goto finish;

      } else if (errno != EEXIST || (semflag & IPC_EXCL) != 0)
         goto err;
      /* else fall through */
   }

   /*
    * (O_CREAT not secified) or
    * (O_CREAT without O_EXCL and semaphore already exists).
    * Must open semaphore and make certain it has been initialized.
    */
   if ( (key = ftok(pathname, 0)) == (key_t) -1)
      goto err;
   if ( (semid = semget(key, 0, semflag)) == -1)
      goto err;

   arg.buf = &seminfo;
   for (i = 0; i < MAX_OPEN_SEM_TRIES; i++) {
      if (semctl(semid, 0, IPC_STAT, arg) == -1)
         goto err;
      if (arg.buf->sem_otime != 0)
         goto finish;
      sleep(1);
   }
   errno = ETIMEDOUT;
err:
   save_errno = errno;     /* don't let semctl() change errno */
   if (semid != -1)
      semctl(semid, 0, IPC_RMID);
   errno = save_errno;
   return(SEM_FAILED);

finish:
   if ( (sem = malloc(sizeof(Local_sem_t))) == NULL)
      goto err;
   sem->sem_semid = semid;
   sem->sem_magic = SEM_MAGIC;
   return(sem);
}


//++=======================================================================
int
Local_sem_wait(Local_sem_t *sem)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes: Most of the code for this routine was copied with minor
//         modifications from W. Richard Stevens book: UNIX Network
//         Programming, Interprocess Communications (Printed in 1999).
//
// L2
//=======================================================================--
{
   struct sembuf  op;

   if (sem->sem_magic != SEM_MAGIC) {
      errno = EINVAL;
      return(-1);
   }

   op.sem_num = 0;
   op.sem_op = -1;
   //op.sem_flg = 0;
   op.sem_flg = SEM_UNDO; // Deviation from Richard's to allow cleanup in case of abnormal termination.
   if (semop(sem->sem_semid, &op, 1) < 0)
      return(-1);
   return(0);
}


//++=======================================================================
int
Local_sem_post(Local_sem_t *sem)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes: Most of the code for this routine was copied with minor
//         modifications from W. Richard Stevens book: UNIX Network
//         Programming, Interprocess Communications (Printed in 1999).
//
// L2
//=======================================================================--
{
   struct sembuf  op;

   if (sem->sem_magic != SEM_MAGIC) {
      errno = EINVAL;
      return(-1);
   }

   op.sem_num = 0;
   op.sem_op = 1;
   op.sem_flg = 0;
   if (semop(sem->sem_semid, &op, 1) < 0)
      return(-1);
   return(0);
}


//++=======================================================================
int
Local_sem_close(Local_sem_t *sem)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes: Most of the code for this routine was copied with minor
//         modifications from W. Richard Stevens book: UNIX Network
//         Programming, Interprocess Communications (Printed in 1999).
//
// L2
//=======================================================================--
{
   if (sem->sem_magic != SEM_MAGIC) {
      errno = EINVAL;
      return(-1);
   }
   sem->sem_magic = 0;     /* just in case */

   free(sem);
   return(0);
}


//++=======================================================================
DWORD
GetTickCount(void)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   struct tms  tm;
   DWORD       tickCount;

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

   // Determine milliseconds per tick if we have not done already
   if (g_milliSecondsPerTicks == 0)
   {
      long  ticksPerSecond;

      ticksPerSecond = sysconf(_SC_CLK_TCK);
      DbgTrace(3, "-GetTickCount- TicksPerSec = %0lX\n", ticksPerSecond);
      g_milliSecondsPerTicks = 1000 / ticksPerSecond;
   }

   // Determine the tickCount as milliseconds
   tickCount = g_milliSecondsPerTicks * times(&tm);

   DbgTrace(2, "-GetTickCount- End, retValue = %0lX\n", tickCount);

   return tickCount;
}


//++=======================================================================
CasaStatus
CreateUserMutex(
   HANDLE *phMutex
   )
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   CasaStatus  retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                           CASA_FACILITY_AUTHTOKEN,
                                           CASA_STATUS_UNSUCCESSFUL);

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

   // We use Named Semaphores to provide this functionality. The semaphore names are
   // linked to the user via its uid.
   if (sprintf(g_userNamedSemName, "/tmp/casa_auth_semuser_%d", geteuid()) != -1)
   {
      // Create or open semaphore to be only used by the effective user
      g_userNamedSem = Local_sem_open((const char*) g_userNamedSemName, O_RDWR | O_CREAT, 0600, 1);
      if (g_userNamedSem == SEM_FAILED)
      {
         DbgTrace(0, "-CreateUserMutex- Error opening named semaphore, errno = %d\n", errno);
      }
      else
      {
         // Success
         retStatus = CASA_STATUS_SUCCESS;
      }
   }
   else
   {
      DbgTrace(0, "-CreateUserMutex- sprintf failed, error = %d\n", errno);
   }

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

   return retStatus;
}


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

   // Wait on the named semaphore
   if (Local_sem_wait(g_userNamedSem) != 0)
   {
      DbgTrace(0, "-AcquireUserMutex- Error returned by sem_wait(), errno = %d\n", errno);
   }

   // The user semaphore has been acquired
   g_userNamedSemAcquired = true;

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


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

   // The user semaphore is no longer acquired
   g_userNamedSemAcquired = false;

   // Post on the named semaphore
   if (Local_sem_post(g_userNamedSem) != 0)
   {
      DbgTrace(0, "-ReleaseUserMutex- Error returned by sem_post(), errno = %d\n", errno);
   }

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


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

   // Do not do anything if the named semaphore is invalid
   if (g_userNamedSem != SEM_FAILED)
   {
      // Close the named semaphore. Note that we want user semaphores to
      // hang around, therefore we will not unlink them. This is per-design as
      // is not a resource leak. If someone has an issue with this, then it can
      // be solved by installing a cron job that cleans up the semaphores for
      // deleted users.
      if (Local_sem_close(g_userNamedSem) != 0)
      {
         DbgTrace(0, "-DestroyUserMutex- Error returned by sem_close(), errno = %d\n", errno);
      }

      // Forget about the semaphore
      g_userNamedSem = SEM_FAILED;
   }

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


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

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

   libHandle = dlopen(pFileName, RTLD_LAZY);
   if (libHandle == NULL)
   {
      DbgTrace(0, "-OpenLibrary- Not able to load library, error = %s\n", dlerror());
   }

   DbgTrace(1, "-OpenLibrary- End, handle = %0lX\n", (long) libHandle);

   return libHandle;
}


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

   dlclose(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 = dlsym(libHandle, pFunctionName);
   if (pFuncPtr == NULL)
   {
      DbgTrace(0, "-GetFunctionPtr- Not able to obtain func ptr, error = %s\n", dlerror());
   }

   DbgTrace(1, "-GetFunctionPtr- End, pFuncPtr = %0lX\n", (long) 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
   pthread_mutex_lock(&g_hNormalizedHostNameCacheMutex);

   // 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_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 = (int) 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", errno);

                     // Not able to resolve the name in DNS, just use the host name as
                     // the normalized name.
                     pEntry->buffLengthRequired = (int) 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", errno);
            }
         }
         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
   pthread_mutex_unlock(&g_hNormalizedHostNameCacheMutex);

   DbgTrace(1, "-NormalizeHostName- End, pNormalizedName = %0lX\n", (long) pNormalizedName);

   return pNormalizedName;
}


//++=======================================================================
CasaStatus
InitializeHostNameNormalization(void)
//
//  Arguments: 
//
//  Returns:   
//
//  Abstract:  
//
//  Notes:
//
// L2
//=======================================================================--
{
   CasaStatus  retStatus = CASA_STATUS_SUCCESS;

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

   // Initialize the cache list head
   InitializeListHead(&normalizedHostNameCacheListHead);

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

   return retStatus;
}


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