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

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

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

// Mechanism OID
gss_OID g_mechOid = GSS_C_NULL_OID;


//++=======================================================================
void
LogGssStatuses(
   IN    char *operation,
   IN    OM_uint32 majorGssStatus,
   IN    OM_uint32 minorGssStatus)
//
//  Arguments: 
//
//  Returns:   
//
//  Description:  
//
// L2
//=======================================================================--
{
   OM_uint32         gssMajStat;
   OM_uint32         gssMinStat;
   gss_buffer_desc   msg = GSS_C_EMPTY_BUFFER;
   OM_uint32         gssMsgCtx;

   // Trace the messages associated with the major status
   gssMsgCtx = 0;
   while (1)
   {
      gssMajStat = gss_display_status(&gssMinStat,
                                      majorGssStatus,
                                      GSS_C_GSS_CODE,
                                      g_mechOid,
                                      &gssMsgCtx,
                                      &msg);
      if (gssMajStat != GSS_S_COMPLETE)
      {
         DbgTrace(0, "-LogGssStatuses- Error obtaining display status\n", 0);
         break;
      }

      // Trace this message
      if (msg.value != NULL)
      {
         DbgTrace(0, "-LogGssStatuses- GSS-API error %s: ", operation);
         DbgTrace(0, "%s\n", (char *)msg.value);
      }

      if (msg.length != 0)
         gss_release_buffer(&gssMinStat, &msg);

      if (!gssMsgCtx)
         break;
   }

   // Trace the messages associated with the minor status
   gssMsgCtx = 0;
   while (1)
   {
      gssMajStat = gss_display_status(&gssMinStat,
                                      minorGssStatus,
                                      GSS_C_MECH_CODE,
                                      g_mechOid,
                                      &gssMsgCtx,
                                      &msg);
      if (gssMajStat != GSS_S_COMPLETE)
      {
         DbgTrace(0, "-LogGssStatuses- Error obtaining display status\n", 0);
         break;
      }

      // Trace this message
      if (msg.value != NULL)
      {
         DbgTrace(0, "-LogGssStatuses- GSS-API error %s: ", operation);
         DbgTrace(0, "%s\n", (char *)msg.value);
      }

      if (msg.length != 0)
         gss_release_buffer(&gssMinStat, &msg);

      if (!gssMsgCtx)
         break;
   }
}


//++=======================================================================
CasaStatus SSCS_CALL
AuthTokenIf_GetAuthToken(
   IN       const void  *pIfInstance,
   IN       const char  *pContext,
   IN       const char  *pMechInfo,
   IN       const char  *pHostName,
   IN       void        *pCredStoreScope,
   INOUT    char        *pTokenBuf,
   INOUT    size_t      *pTokenBufLen)
//
// Arguments:  
//    pIfInstance -
//       Pointer to interface object.
//   
//    pServiceConfigIf -
//       Pointer to service config object to which the client is trying to
//       authenticate.
//               
//    pContext -
//       Pointer to null terminated string containing mechanism specific
//       context information. Another name for context is Authentication
//       Realm.
//
//    pMechInfo -
//       Pointer to null terminated string containing mechanism specific
//       information. This is information is provided by the server to
//       aid the mechanism to generate an authentication token. For
//       example, the mechanism information for a Kerberos mechanism
//       may be the service principal name to which the user will be
//       authenticating.
//               
//    pHostName -
//       Pointer to null terminated string containing the name of the
//       host where the ATS resides.
//   
//    pCredStoreScope -
//       Pointer to CASA structure for scoping credential store access
//       to specific users. This can only be leveraged when running in
//       the context of System under Windows.
//   
//    pTokenBuf -
//       Pointer to buffer that will receive the authentication
//       token. The length of this buffer is specified by the
//       pTokenBufLen parameter. Note that the the authentication
//       token will be in the form of a NULL terminated string.
//
//    pTokenBufLen -
//       Pointer to integer that contains the length of the
//       buffer pointed at by pTokenBuf. 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 pUserNameBuf is
//       not large enough.
//   
// Returns:
//    Casa Status
//                           
// Description:
//    Get authentication token to authenticate user to specified service.
//
// L2
//=======================================================================--
{
   CasaStatus        retStatus;
   char              *pKrbServiceName = NULL;
   bool              freeKrbSvcNameBuf = false;
   OM_uint32         gssMajStat;
   OM_uint32         gssMinStat;
   gss_buffer_desc   gssBuffer;
   gss_name_t        gssServiceName;

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

   // Validate input parameters
   if (pIfInstance == NULL
       || pContext == NULL
       || pHostName == NULL
       || pTokenBufLen == NULL
       || (pTokenBuf == NULL && *pTokenBufLen != 0))
   {
      DbgTrace(0, "-AuthTokenIf_GetAuthToken- Invalid input parameter\n", 0);

      retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                  CASA_FACILITY_KRB5TOKEN,
                                  CASA_STATUS_INVALID_PARAMETER);
      goto exit;
   }

   // Process any mechanism information that may have been provided
   if (pMechInfo)
   {
      // Allocate a buffer to hold the mech info so that we can manipulate it
      char *pMechInfoInt = malloc(strlen(pMechInfo) + 1);
      if (pMechInfoInt)
      {
         char *pNextSettingToken;
         char *pSettingValueToken;

         // Copy the mechanism info to our work buffer
         strcpy(pMechInfoInt, pMechInfo);

         // Mechanism information has been provided. Mechanism information
         // consists of semicolon delimited settings. The settings are formated
         // using the format settingName=settingvalue. No white space is allowed
         // as part of the mechanism information.
         pSettingValueToken = strtok_r(pMechInfoInt, ";", &pNextSettingToken);
         while (pSettingValueToken != NULL)
         {
            char *pNextToken;
            char *pSettingName = strtok_r(pSettingValueToken, "=", &pNextToken);
            char *pSettingValue = strtok_r(NULL, "=", &pNextToken);
            if (pSettingValue)
            {
               // Process the setting
               if (strcasecmp(pSettingName, "SVC_PRINCIPAL") == 0)
               {
                  pKrbServiceName = pSettingValue;
               }
            }
            else
            {
               DbgTrace(0, "-AuthTokenIf_GetAuthToken- Bad setting\n", 0);
            }

            pSettingValueToken = strtok_r(NULL, ";", &pNextSettingToken);
         }

         // Free the buffer that we allocated
         free(pMechInfoInt);
      }
      else
      {
         DbgTrace(0, "-AuthTokenIf_GetAuthToken- Buffer allocation failure\n", 0);
         retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                     CASA_FACILITY_PWTOKEN,
                                     CASA_STATUS_INVALID_PARAMETER);
         goto exit;
      }
   }

   // Check if we need to construct the service name
   if (pKrbServiceName == NULL
       || strlen(pKrbServiceName) == 0)
   {
      // The service name will default to host/hostname
      pKrbServiceName = malloc(5 /*"host/"*/ + strlen(pHostName) + 1 /*'/0'*/);
      if (pKrbServiceName)
      {
         freeKrbSvcNameBuf = true;
         sprintf(pKrbServiceName, "host/%s", pHostName);
      }
      else
      {
         DbgTrace(0, "-AuthTokenIf_GetAuthToken- Memory allocation failure\n", 0);
         goto exit;
      }
   }

   // Import the service principal name into something that
   // GSS-API can understand based on its form.
   gssBuffer.value = (void*) pKrbServiceName;
   gssBuffer.length = strlen(pKrbServiceName) + 1;
   if (strchr(pKrbServiceName, '@') != NULL)
   {
      // The name is of the form "servicename@realmname"
      gssMajStat = gss_import_name(&gssMinStat,
                                   &gssBuffer,
                                   (gss_OID) GSS_C_NT_HOSTBASED_SERVICE,
                                   &gssServiceName);
   }
   else
   {
      // The name is of the form "servicename"
      gssMajStat = gss_import_name(&gssMinStat,
                                   &gssBuffer,
                                   (gss_OID) GSS_C_NT_USER_NAME,
                                   &gssServiceName);
   }

   // Proceed based on the result of the name import operation
   if (gssMajStat == GSS_S_COMPLETE)
   {
      // Establish a context
      gss_ctx_id_t      gssContext = GSS_C_NO_CONTEXT;
      gss_buffer_desc   gssSendToken = {0};
      OM_uint32         gssRetFlags;
      gssMajStat = gss_init_sec_context(&gssMinStat,
                                        GSS_C_NO_CREDENTIAL,
                                        &gssContext,
                                        gssServiceName,
                                        g_mechOid,
                                        0, // Flags
                                        0,
                                        NULL, // no channel bindings
                                        GSS_C_NO_BUFFER,  // no token from peer
                                        NULL, // ignore mech type
                                        &gssSendToken,
                                        &gssRetFlags,
                                        NULL);   // ignore time rec

      // Proceed based on the result of the gss_init_sec_context operation
      if (gssMajStat == GSS_S_COMPLETE
          && gssSendToken.length != 0)
      {
         // Make sure that the token is not too large
         if (gssSendToken.length <= UINT32_MAX)
         {
            char        *pEncodedToken;
            uint32_t    encodedTokenLen;

            // The security context was initialized, now return the token to the
            // caller after base64 encoding it.
            retStatus = EncodeData(gssSendToken.value,
                                   gssSendToken.length,
                                   &pEncodedToken,
                                   &encodedTokenLen);
            if (CASA_SUCCESS(retStatus))
            {
               // Verify that the caller provided a buffer that is big enough
               if (encodedTokenLen > *pTokenBufLen)
               {
                  // At least one of the supplied buffers is not big enough
                  DbgTrace(1, "-AuthTokenIf_GetAuthToken- Insufficient buffer space provided\n", 0);

                  retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                              CASA_FACILITY_KRB5TOKEN,
                                              CASA_STATUS_BUFFER_OVERFLOW);
               }
               else
               {
                  // The buffer provided is large enough, copy the data and return the actual size.
                  memcpy((void*) pTokenBuf, pEncodedToken, encodedTokenLen);

                  // Success
                  retStatus = CASA_STATUS_SUCCESS;
               }

               // Return the actual size or the size required
               *pTokenBufLen = encodedTokenLen;

               // Free the buffer containing the encoded token
               free(pEncodedToken);
            }
            else
            {
               DbgTrace(1, "-AuthTokenIf_GetAuthToken- Encoding failed\n", 0);
            }
         }
         else
         {
            DbgTrace(0, "-AuthTokenIf_GetAuthToken- GSS Token too large\n", 0);
            retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                        CASA_FACILITY_KRB5TOKEN,
                                        CASA_STATUS_UNSUCCESSFUL);
         }
      }
      else
      {
         DbgTrace(0, "-AuthTokenIf_GetAuthToken- Error initing sec context\n", 0);
         LogGssStatuses("initializing context", gssMajStat, gssMinStat);

         retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                     CASA_FACILITY_KRB5TOKEN,
                                     CASA_STATUS_UNSUCCESSFUL);
      }

      // Release send token buffer if necessary
      if (gssSendToken.length != 0)
         gss_release_buffer(&gssMinStat, &gssSendToken);


      // Free context if necessary
      if (gssContext != GSS_C_NO_CONTEXT)
         gss_delete_sec_context(&gssMinStat, &gssContext, GSS_C_NO_BUFFER);

      // Release the buffer associated with the service name
      gss_release_name(&gssMinStat, &gssServiceName);
   }
   else
   {
      DbgTrace(0, "-AuthTokenIf_GetAuthToken- Error importing service name\n", 0);
      LogGssStatuses("importing service name", gssMajStat, gssMinStat);

      retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                  CASA_FACILITY_KRB5TOKEN,
                                  CASA_STATUS_OBJECT_NOT_FOUND);
   }

exit:

   // Free buffer holding the Krb Service Name if necessary
   if (freeKrbSvcNameBuf)
      free(pKrbServiceName);

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

   return retStatus;
}


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

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

   // Nothing to do at this time.

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

   return retStatus;
}


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