/***********************************************************************
 * 
 *  Copyright (C) 2005-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.
 * 
 ***********************************************************************/


//===[ Include files ]=====================================================

#include "internal.h"

//===[ Type definitions ]==================================================

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

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


//++=======================================================================
CasaStatus SSCS_CALL
Krb5AuthTokenIf_GetAuthTokenCredentials(
   IN       const void        *pIfInstance,
   IN       const ConfigIf    *pServiceConfigIf,
   INOUT    const char        *pUserNameBuf,
   INOUT    int               *pUserNameBufLen,
   INOUT    const char        *pTokenBuf,
   INOUT    int               *pTokenBufLen)
//
// Arguments:  
//    pIfInstance -
//       Pointer to interface object.
//   
//    pServiceConfigIf -
//       Pointer to service config object to which the client is trying to
//       authenticate.
//               
//    pUserNameBuf -
//       Pointer to buffer that will receive a string with the
//       username that should used when authenticating to the
//       service. The length of this buffer is specified by the
//       pUserNameBufLen parameter. Note that the string
//       returned will be NULL terminated.
//
//    pUserNameBufLen -
//       Pointer to integer that contains the length of the
//       buffer pointed at by pUserNameBuf. Upon return of the
//       function, the integer will contain the actual length
//       of the username string (including the NULL terminator)
//       if the function successfully completes or the buffer
//       length required if the function fails because the buffer
//       pointed at by either pUserNameBuf or pTokenBuf is not
//       large enough.
//               
//    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 either pUserNameBuf
//       or pTokenBuf is not large enough.
//   
// Returns:
//    Casa Status
//                           
// Description:
//    Get authentication token credentials to authenticate user to specified
//    service.
//
// L2
//=======================================================================--
{
   CasaStatus        retStatus;
   OM_uint32         gssMajStat;
   OM_uint32         gssMinStat;
   char              *pKrbServiceName;


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

   // Validate input parameters
   if (pIfInstance == NULL
       || pServiceConfigIf == NULL
       || pUserNameBufLen == NULL
       || (pUserNameBuf == NULL && *pUserNameBufLen != 0)
       || pTokenBufLen == NULL
       || (pTokenBuf == NULL && *pTokenBufLen != 0))
   {
      DbgTrace(0, "krb5_token -Krb5AuthTokenIf_GetAuthTokenCredentials- Invalid input parameter\n", 0);

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

   // Map service name to Krb Service Principal Name
   pKrbServiceName = pServiceConfigIf->getEntryValue(pServiceConfigIf, "KerberosPrincipal");
   if (pKrbServiceName)
   {
      gss_buffer_desc   gssBuffer;
      gss_name_t        gssServiceName;

      // 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@hostname"
         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)
         {
            // Now get the username
            gss_name_t    gssClient;
            gssMajStat = gss_inquire_context(&gssMinStat,
                                             gssContext,
                                             &gssClient,
                                             NULL,
                                             NULL,
                                             NULL,
                                             NULL,
                                             NULL,
                                             NULL);
            if (gssMajStat == GSS_S_COMPLETE)
            {
               gss_buffer_desc   gssDisplayName;
               gss_OID           gssNameType; 
               gssMajStat = gss_display_name(&gssMinStat, gssClient, &gssDisplayName, &gssNameType);
               if (gssMajStat == GSS_S_COMPLETE)
               {
                  char    *pEncodedToken;
                  int      encodedTokenLen;

                  // Base64 encode the token
                  retStatus = EncodeData(gssSendToken.value,
                                         gssSendToken.length,
                                         &pEncodedToken,
                                         &encodedTokenLen);
                  if (CASA_SUCCESS(retStatus))
                  {
                     // Verify that the caller provided buffers that are big enough
                     if (gssDisplayName.length > *pUserNameBufLen
                         || encodedTokenLen > *pTokenBufLen)
                     {
                        // At least one of the supplied buffers is not big enough
                        DbgTrace(1, "krb5_token -Krb5AuthTokenIf_GetAuthTokenCredentials- Insufficient buffer space provided\n", 0);

                        retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                                    CASA_FACILITY_KRB5TOKEN,
                                                    CASA_STATUS_BUFFER_OVERFLOW);
                     }
                     else
                     {
                        // The buffers provided are large enough, copy the data and return the actual sizes.
                        memcpy((void*) pUserNameBuf, gssDisplayName.value, gssDisplayName.length + 1);
                        memcpy((void*) pTokenBuf, pEncodedToken, encodedTokenLen +1);

                        // Success
                        retStatus = CASA_STATUS_SUCCESS;
                     }

                     // Return the actual sizes or the sizes required
                     *pUserNameBufLen = gssDisplayName.length + 1;
                     *pTokenBufLen = encodedTokenLen;

                     // Free the buffer containing the encoded token
                     free(pEncodedToken);
                  }
                  else
                  {
                     DbgTrace(1, "krb5_token -Krb5AuthTokenIf_GetAuthTokenCredentials- Encoding failed\n", 0);
                  }

                  // Release the buffer associated with the client display name
                  gss_release_buffer(&gssMinStat, &gssDisplayName);
               }
               else
               {
                  DbgTrace(0, "krb5_token -Krb5AuthTokenIf_GetAuthTokenCredentials- Error obtaining display name\n", 0);
                  LogGssStatuses("obtaining display name", gssMajStat, gssMinStat);

                  retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                              CASA_FACILITY_KRB5TOKEN,
                                              CASA_STATUS_UNSUCCESSFUL);
               }
            }
            else
            {
               DbgTrace(0, "krb5_token -Krb5AuthTokenIf_GetAuthTokenCredentials- Error inquiring context\n", 0);
               LogGssStatuses("inquiring context", gssMajStat, gssMinStat);

               retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                           CASA_FACILITY_KRB5TOKEN,
                                           CASA_STATUS_UNSUCCESSFUL);
            }
         }
         else
         {
            DbgTrace(0, "krb5_token -Krb5AuthTokenIf_GetAuthTokenCredentials- 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, "krb5_token -Krb5AuthTokenIf_GetAuthTokenCredentials- 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);
      }

      // Free the krb service principal name
      free(pKrbServiceName);
   }
   else
   {
      DbgTrace(0, "krb5_token -Krb5AuthTokenIf_GetAuthTokenCredentials- No Kerberos principal name configured\n", 0);
      retStatus = CasaStatusBuild(CASA_SEVERITY_ERROR,
                                  CASA_FACILITY_KRB5TOKEN,
                                  CASA_STATUS_CONFIGURATION_ERROR);
   }

exit:

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

   return retStatus;
}


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