//+-----------------------------------------------------------------------
//
// Microsoft Windows
//
// Copyright (c) Microsoft Corporation 2001
//
// File:        krbaudit.cxx
//
// Contents:    Auditing routines
//
//
// History:     27-April-2001   Created         kumarp
//
//------------------------------------------------------------------------

#include <kerb.hxx>
#include <kerbp.h>
#include <krbaudit.h>


//+-------------------------------------------------------------------------
//
//  Function:   KerbGetLogonGuid
//
//  Synopsis:   Gets a unique ID based on certain fields in the ticket
//
//  Arguments:  pPrimaryCredentials -- primary credentials
//              pKdcReplyBody       -- kdc reply structure
//              pLogonGuid          -- returned GUID
//
//  Returns:    NTSTATUS code
//
//  Notes:      The generated GUID is MD5 hash of 3 fields:
//                -- client name
//                -- client realm
//                -- ticket start time
//                
//              All of these fields are available at client/kdc/server machine.
//              this allows us to generate the same unique ID on these machines
//              without having to introduce a new field in the ticket.
//              This GUID is used in audit events allowing us to correlate
//              events on three different machines.
//
//--------------------------------------------------------------------------
NTSTATUS
KerbGetLogonGuid(
    IN  PKERB_PRIMARY_CREDENTIAL  pPrimaryCredentials,
    IN  PKERB_ENCRYPTED_KDC_REPLY pKdcReplyBody,
    OUT LPGUID pLogonGuid
    )
{
    NTSTATUS Status = STATUS_INVALID_PARAMETER;

    // ISSUE-2001/04/28-kumarp : use KERB_ENCRYPTED_KDC_REPLY_starttime_present
    //if ( KdcReplyBody->flags & KERB_ENCRYPTED_KDC_REPLY_starttime_present )
    if ( pKdcReplyBody && pPrimaryCredentials )
    {

        Status = LsaIGetLogonGuid(
                     &pPrimaryCredentials->UserName,
                     &pPrimaryCredentials->DomainName,
                     (PBYTE) &pKdcReplyBody->starttime,
                     sizeof(KERB_TIME),
                     pLogonGuid
                     );

        if (!NT_SUCCESS(Status))
        {
            RtlZeroMemory( pLogonGuid, sizeof(GUID) );
        }
    }
    
    return Status;
}
    


//+-------------------------------------------------------------------------
//
//  Function:   KerbGenerateAuditForLogonUsingExplicitCreds
//
//  Synopsis:   Generate an audit event to indicate that somebody logged on
//              by supplying explicit credentials.
//
//  Arguments:  pCurrentUserLogonSession   -- logon session of the user
//                                            who supplied credentials of
//                                            another user
//              pNewUserPrimaryCredentials -- supplied primary credentials
//              pNewUserLogonGuid          -- logon GUID for the new logon
//
//  Returns:    NTSTATUS code
//
//  Notes:      This event covers credentials obtain from credman
//
//--------------------------------------------------------------------------
NTSTATUS
KerbGenerateAuditForLogonUsingExplicitCreds(
    IN PKERB_LOGON_SESSION pCurrentUserLogonSession,
    IN PKERB_PRIMARY_CREDENTIAL pNewUserPrimaryCredentials,
    IN LPGUID pNewUserLogonGuid
      )
{
    NTSTATUS Status = STATUS_SUCCESS;
    GUID CurrentUserLogonGuid;
    LPGUID pCurrentUserLogonGuid = NULL;
    PKERB_TICKET_CACHE_ENTRY pTicketCacheEntry = NULL;
    UNICODE_STRING OurDomainName = { 0 };
    KERB_TIME CurrentUserStartTime = { 0 };

    //
    // calculate LogonGuid for current logged on user
    // we need the following 3 parameters for this:
    //   -- client name
    //   -- client realm
    //   -- ticket start time
    //
    if ( !(pCurrentUserLogonSession->LogonSessionFlags &
           (KERB_LOGON_LOCAL_ONLY | KERB_LOGON_DEFERRED)) )
    {
        Status = KerbGetOurDomainName( &OurDomainName );

        ASSERT( NT_SUCCESS(Status) && L"KerbGenerateAuditForLogonUsingExplicitCreds: KerbGetOurDomainName failed" );
        
        if (NT_SUCCESS(Status))
        {
            //
            // find the cached ticket for the local machine from
            // the ticket cache of the current logon session.
            //
            pTicketCacheEntry =
                KerbLocateTicketCacheEntry(
                    &pCurrentUserLogonSession->PrimaryCredentials.ServerTicketCache,
                    KerbGlobalMitMachineServiceName,
                    &OurDomainName
                    );

            if ( pTicketCacheEntry )
            {
                //
                // Convert start time to the format we want.
                //
                KerbConvertLargeIntToGeneralizedTime(
                    &CurrentUserStartTime,
                    NULL,
                    &pTicketCacheEntry->StartTime
                    );

                //
                // Generate the logon GUID
                //
                Status = LsaIGetLogonGuid(
                             &pCurrentUserLogonSession->PrimaryCredentials.UserName,
                             &pCurrentUserLogonSession->PrimaryCredentials.DomainName,
                             (PBYTE) &CurrentUserStartTime,
                             sizeof(KERB_TIME),
                             &CurrentUserLogonGuid
                             );
                if (NT_SUCCESS(Status))
                {
                    pCurrentUserLogonGuid = &CurrentUserLogonGuid;
                }
            }
            else
            {
                D_DebugLog((DEB_TRACE, "KerbGenerateAuditForLogonUsingExplicitCreds: could not locate ticket"));
            }                                                                                                   
        }
                                       
        KerbFreeString(&OurDomainName);

    }
#if DBG
    else
    {
        //
        // KERB_LOGON_LOCAL_ONLY indicates a logon using non Kerberos package.
        // Logon GUID is supported only by Kerberos therefore its generation
        // is skipped.
        //
        if (pCurrentUserLogonSession->LogonSessionFlags & KERB_LOGON_LOCAL_ONLY)
        {
            D_DebugLog((DEB_TRACE,"KerbGenerateAuditForLogonUsingExplicitCreds: LogonSession %p has KERB_LOGON_LOCAL_ONLY\n", pCurrentUserLogonSession));
        }

        //
        // KERB_LOGON_DEFERRED indicates that a logon occurred using
        // cached credentials because kdc was not available.  In this case,
        // we will not have a ticket for local machine therefore generation of
        // the logon GUID is skipped.
        //
        if (pCurrentUserLogonSession->LogonSessionFlags & KERB_LOGON_DEFERRED)
        {
            D_DebugLog((DEB_TRACE,"KerbGenerateAuditForLogonUsingExplicitCreds: LogonSession %p has KERB_LOGON_DEFERRED\n", pCurrentUserLogonSession));
        }
    }
#endif

    //
    // now generate the audit
    //
    Status = I_LsaIAuditLogonUsingExplicitCreds(
                 EVENTLOG_AUDIT_SUCCESS,
                 NULL,          // no user sid
                 &pCurrentUserLogonSession->PrimaryCredentials.UserName,
                 &pCurrentUserLogonSession->PrimaryCredentials.DomainName,
                 &pCurrentUserLogonSession->LogonId,
                 pCurrentUserLogonGuid,
                 &pNewUserPrimaryCredentials->UserName,
                 &pNewUserPrimaryCredentials->DomainName,
                 pNewUserLogonGuid
                 );

    return Status;
}

//+-------------------------------------------------------------------------
//
//  Function:   KerbAuditLogon
//
//  Synopsis:   Generate a successful logon audit event
//
//  Arguments:  LogonStatus      -- overall logon status
//              LogonSubStatus   -- sub-category within a failed logon status
//              pEncryptedTicket -- ticket used
//              pUserSid         -- user's SID
//              pWorkstationName -- logon workstation
//              pLogonId         -- logon LUID
//
//  Returns:    NTSTATUS code
//
//  Notes:      A new field (logon GUID) was added to this audit event.
//              In order to send this new field to LSA, we had two options:
//              1) add new function (AuditLogonEx) to LSA dispatch table
//              2) define a private (LsaI) function to do the job
//
//             option#2 was chosen because the logon GUID is a Kerberos only
//             feature.
//
//--------------------------------------------------------------------------
NTSTATUS
KerbAuditLogon(
    IN NTSTATUS LogonStatus,
    IN NTSTATUS LogonSubStatus,
    IN PKERB_ENCRYPTED_TICKET pEncryptedTicket,
    IN PSID pUserSid,
    IN PUNICODE_STRING pWorkstationName,
    IN PLUID pLogonId
    )
{
    GUID LogonGuid = { 0 };
    NTSTATUS Status = STATUS_SUCCESS;
    //PKERB_TIME pStartTime;
    UNICODE_STRING ClientRealm = { 0 };
    UNICODE_STRING ClientName = { 0 };
    KERBERR KerbErr = KDC_ERR_NONE;
    ULONG NameType;

    //
    // convert kerb style names to UNICODE_STRINGs
    //
    KerbErr = KerbConvertRealmToUnicodeString(
                  &ClientRealm,
                  &pEncryptedTicket->client_realm );
    if ( KerbErr == KDC_ERR_NONE )
    {
        KerbErr = KerbConvertPrincipalNameToString(
                      &ClientName,
                      &NameType,
                      &pEncryptedTicket->client_name
                      );
    }

    if ( KerbErr != KDC_ERR_NONE )
    {
        Status = KerbMapKerbError( KerbErr );
        goto Cleanup;
    }

    //
    // generate the logon GUID
    //
    Status = I_LsaIGetLogonGuid(
                 &ClientName,
                 &ClientRealm,
                 (PBYTE)&pEncryptedTicket->KERB_ENCRYPTED_TICKET_starttime,
                 sizeof(KERB_TIME),
                 &LogonGuid
                 );

    if (!NT_SUCCESS(Status))
    {
        goto Cleanup;
    }
    
        
    //
    // generate successful logon audit. use the special 
    // LsaIAuditKerberosLogon function so that we can pass in
    // the generated unique logon guid
    //
    I_LsaIAuditKerberosLogon(
        LogonStatus,
        LogonSubStatus,
        &ClientName,
        &ClientRealm,
        pWorkstationName,
        pUserSid,
        Network,
        &KerberosSource,
        pLogonId,
        &LogonGuid
        );
    
 Cleanup:
    if (ClientRealm.Buffer != NULL)
    {
        KerbFreeString( &ClientRealm );
    }

    if (ClientName.Buffer != NULL)
    {
        KerbFreeString( &ClientName );
    }

    if ( !NT_SUCCESS( Status ) )
    {
        D_DebugLog((DEB_ERROR,"KerbAuditLogon: failed: %x\n", Status ));
    }


    return Status;
}