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


//++=======================================================================
static
CasaStatus
GetUserCredentials(
   IN       const char *pRealm,
   IN       void *pCredStoreScope,
   IN       bool realm_credentials_only,
   INOUT    char **ppUsername,
   INOUT    char **ppPassword)
//
// Arguments:  
//    pRealm -
//       The realm to which the credentials apply.
//   
//    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.
//   
//    realm_credentials_only -
//       Only utilize credentials associated with the specified realm.
//               
//    ppUsername -
//       Pointer to variable that will receive buffer with the username.
//               
//    ppPassword -
//       Pointer to variable that will receive buffer with the password.
//   
// Returns:
//    Casa Status
//                           
// Description:
//    Get authentication credentials for the specified realm.
//
// L2
//=======================================================================--
{
   CasaStatus              retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                                       CASA_FACILITY_PWTOKEN,
                                                       CASA_STATUS_UNSUCCESSFUL);
   char                    *pUsername;
   char                    *pPassword;
   int                     rcode = NSSCS_E_OBJECT_NOT_FOUND;
   uint32_t                credtype = SSCS_CRED_TYPE_BASIC_F;
   SSCS_BASIC_CREDENTIAL   credential = {0};
   SSCS_SECRET_ID_T        secretId = {0};
   size_t                  secretIdLen;
   
   DbgTrace(1, "-GetUserCredentials- Start\n", 0);
   DbgTrace(2, "-GetUserCredentials- Realm = %s\n", pRealm);
   DbgTrace(2, "-GetUserCredentials- CredStoreScope = %X\n", pCredStoreScope);
   DbgTrace(2, "-GetUserCredentials- RealmCredsOnly = %X\n", realm_credentials_only);

   // Initialize output parameters
   *ppUsername = NULL;
   *ppPassword = NULL;

   // Get the length of the realm string into the secret id structure
   // and verify thatr it is not too long.
   secretIdLen = sscs_Utf8Strlen(pRealm) + 1;
   if (secretIdLen <= UINT32_MAX)
   {
      secretId.len = secretIdLen;
      if (secretId.len <= NSSCS_MAX_SECRET_ID_LEN)
      {
         // Set the secret id in the structure
         sscs_Utf8Strcpy((char*) secretId.id, pRealm);

         // Specify that we want the common name
         credential.unFlags = USERNAME_TYPE_CN_F;

         // Now try to get the credentials
         rcode = miCASAGetCredential(0,
                                     &secretId,
                                     NULL,
                                     &credtype,
                                     &credential,
                                     (SSCS_EXT_T*) pCredStoreScope);
         if (rcode != NSSCS_SUCCESS
             && realm_credentials_only == false)
         {
            // There were no credentials for the realm, now try to obtain the
            // desktop credentials.
            secretId.len = sscs_Utf8Strlen("Desktop") + 1;
            if (secretId.len <= NSSCS_MAX_SECRET_ID_LEN)
            {
               sscs_Utf8Strcpy((char*) secretId.id, "Desktop");
               rcode = miCASAGetCredential(0,
                                           &secretId,
                                           NULL,
                                           &credtype,
                                           &credential,
                                           (SSCS_EXT_T*) pCredStoreScope);
            }
            else
            {
               DbgTrace(0, "-GetUserCredentials- Desktop name too long\n", 0);
               retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                           CASA_FACILITY_PWTOKEN,
                                           CASA_STATUS_UNSUCCESSFUL);
            }
         }
      }
      else
      {
         DbgTrace(0, "-GetUserCredentials- Realm name too long\n", 0);
         retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                     CASA_FACILITY_PWTOKEN,
                                     CASA_STATUS_UNSUCCESSFUL);
      }
   }
   else
   {
      DbgTrace(0, "-GetUserCredentials- Realm name too long\n", 0);
      retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                  CASA_FACILITY_PWTOKEN,
                                  CASA_STATUS_UNSUCCESSFUL);
   }

   // Proceed based on the result of the operatiosn above
   if (rcode == NSSCS_SUCCESS
       && credential.username != NULL
       && credential.password != NULL)
   {
      // Allocate a buffer to return the username
      pUsername = (char*) malloc(strlen((char*) credential.username) + 1);
      if (pUsername)
      {
         // Copy the username into the buffer that we will be returning
         strcpy(pUsername, (char*) credential.username);

         // Allocate a buffer to return the password
         pPassword = (char*) malloc(strlen((char*) credential.password) + 1);
         if (pPassword)
         {
            // Copy the password into the buffer that we will be returning
            strcpy(pPassword, (char*) credential.password);

            DbgTrace(1, "-GetUserCredentials- Username = %s\n", pUsername);

            // Success
            retStatus = CASA_STATUS_SUCCESS;
         }
         else
         {
            DbgTrace(0, "-GetUserCredentials- Buffer allocation error\n", 0);
            retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                        CASA_FACILITY_PWTOKEN,
                                        CASA_STATUS_INSUFFICIENT_RESOURCES);

            // Free the buffer allocated for the username
            free(pUsername);
         }
      }
      else
      {
         DbgTrace(0, "-GetUserCredentials- Buffer allocation error\n", 0);
         retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                     CASA_FACILITY_PWTOKEN,
                                     CASA_STATUS_INSUFFICIENT_RESOURCES);
      }
   }
   else
   {
      DbgTrace(0, "-GetUserCredentials- Failed to obtain credentials for pw authentication, code = %d\n", rcode);
   }

   // Clear out the credential structure to make sure that we are not leaving sensitive
   // information on the stack.
   memset(&credential, 0, sizeof(credential));

   // Return the buffers to the caller if successful
   if (CASA_SUCCESS(retStatus))
   {
      *ppUsername = pUsername;
      *ppPassword = pPassword;
   }

   DbgTrace(1, "-GetUserCredentials- End, retStatus = %0X\n", retStatus);

   return retStatus;
}


//++=======================================================================
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.
//   
//    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        *pUsername = NULL;
   char        *pPassword = NULL;
   char        *pToken;
   bool        realm_credentials_only = false;

   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_PWTOKEN,
                                  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, "REALM_CREDENTIALS_ONLY") == 0)
               {
                  if (strcasecmp(pSettingValue, "true") == 0)
                  {
                     realm_credentials_only = true;
                  }
               }
            }
            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;
      }
   }

   // Get the user credentials
   retStatus = GetUserCredentials(pContext,
                                  pCredStoreScope,
                                  realm_credentials_only,
                                  &pUsername,
                                  &pPassword);
   if (CASA_SUCCESS(retStatus))
   {
      size_t   tokenLen = strlen(pUsername) + 2 + strlen(pPassword) + 2 + 1;

      // Make sure that the token is not too large
      if (tokenLen <= UINT32_MAX)
      {
         // Now construct the PW token with the following format:
         // "username\r\n" + "password\r\n"
         //
         // First allocate a buffer large enough to hold the token
         pToken = (char*) malloc(strlen(pUsername) + 2 + strlen(pPassword) + 2 + 1);
         if (pToken)
         {
            char     *pEncodedToken;
            uint32_t encodedTokenLen;

            // Now assemble the token
            sprintf(pToken, "%s\r\n%s\r\n", pUsername, pPassword);

            // The token has been assembled, now encode it.
            retStatus = EncodeData(pToken,
                                   (const uint32_t) tokenLen,
                                   &pEncodedToken,
                                   &encodedTokenLen);
            if (CASA_SUCCESS(retStatus))
            {
               // Verify that the caller provided a buffer that is big enough
               if (encodedTokenLen > *pTokenBufLen)
               {
                  // The buffer is not big enough
                  retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                              CASA_FACILITY_PWTOKEN,
                                              CASA_STATUS_BUFFER_OVERFLOW);
               }
               else
               {
                  // The buffer provided is large enough, copy the data.
                  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 after clearing
               // it to avoid leaking sensitive information.
               memset(pEncodedToken, 0, strlen(pEncodedToken));
               free(pEncodedToken);
            }
            else
            {
               DbgTrace(1, "-AuthTokenIf_GetAuthToken- Encoding failed\n", 0);
            }

            // Free the buffer allocated for the token after clearing it
            // to avoid leaving sensitive information behind.
            memset(pToken, 0, strlen(pToken));
            free(pToken);
         }
         else
         {
            DbgTrace(0, "-AuthTokenIf_GetAuthToken- Buffer allocation error\n", 0);
            retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                        CASA_FACILITY_PWTOKEN,
                                        CASA_STATUS_INSUFFICIENT_RESOURCES);
         }
      }
      else
      {
         DbgTrace(0, "-AuthTokenIf_GetAuthToken- Token too large\n", 0);
         retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                     CASA_FACILITY_KRB5TOKEN,
                                     CASA_STATUS_UNSUCCESSFUL);
      }

      // Free allocated buffers after clearing memory holding the password
      free(pUsername);
      memset(pPassword, 0, strlen(pPassword));
      free(pPassword);
   }
   else
   {
      DbgTrace(1, "-AuthTokenIf_GetAuthToken- Failed to obtain the user credentials\n", 0);
   }

exit:

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

   return retStatus;
}


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