//+-----------------------------------------------------------------------
//
// Microsoft Windows
//
// Copyright (c) Microsoft Corporation 1999
//
// File:        events.c
//
// Contents:    Schannel event log functions.
//
// Functions:   SchInitializeEvents
//              SchReportEvent
//              SchShutdownEvents
//
// History:     03-05-99   jbanes    Created
//
//------------------------------------------------------------------------
#include "sslp.h"
#include <lsapmsgs.h>
#include <netlib.h>

HANDLE g_hEventLog = NULL;
HANDLE g_hDiscardDupEventLog = NULL;

WCHAR   EventSourceName[] = TEXT("Schannel");

#define MAX_EVENT_STRINGS       8

#define SCH_MESSAGE_FILENAME    TEXT("%SystemRoot%\\system32\\lsasrv.dll")

LPWSTR pszClientString = NULL;
LPWSTR pszServerString = NULL;

NTSTATUS
SchGetMessageString(
    LPVOID   Resource,
    DWORD    Index,
    LPWSTR * pRetString);


//+---------------------------------------------------------------------------
//
//  Function:   SchInitializeEvents
//
//  Synopsis:   Connects to event log service.
//
//  Arguments:  (none)
//
//  History:    03-05-99   jbanes    Created
//
//  Notes:
//
//----------------------------------------------------------------------------
BOOL
SchInitializeEvents(void)
{
    HKEY     hKey;
    int      err;
    DWORD    disp;
    HMODULE  hResource;

    //
    // Create registry entries, whether event logging is currently
    // enabled or not.
    //

    err = RegCreateKeyEx(   HKEY_LOCAL_MACHINE,
                            TEXT("System\\CurrentControlSet\\Services\\EventLog\\System\\Schannel"),
                            0,
                            TEXT(""),
                            REG_OPTION_NON_VOLATILE,
                            KEY_WRITE,
                            NULL,
                            &hKey,
                            &disp);
    if(err)
    {
        return(FALSE);
    }

    if (disp == REG_CREATED_NEW_KEY)
    {
        RegSetValueEx(  hKey,
                        TEXT("EventMessageFile"),
                        0,
                        REG_EXPAND_SZ,
                        (PBYTE)SCH_MESSAGE_FILENAME,
                        sizeof(SCH_MESSAGE_FILENAME) );

//        RegSetValueEx(  hKey,
//                        TEXT("CategoryMessageFile"),
//                        0,
//                        REG_EXPAND_SZ,
//                        (PBYTE)SCH_MESSAGE_FILENAME,
//                        sizeof(SCH_MESSAGE_FILENAME) );

        disp = 7;
        RegSetValueEx(  hKey,
                        TEXT("TypesSupported"),
                        0,
                        REG_DWORD,
                        (PBYTE) &disp,
                        sizeof(DWORD) );

//        disp = CATEGORY_MAX_CATEGORY - 1;
//        RegSetValueEx(  hKey,
//                        TEXT("CategoryCount"),
//                        0,
//                        REG_DWORD,
//                        (PBYTE) &disp,
//                        sizeof(DWORD) );

        RegFlushKey(hKey);
    }

    RegCloseKey(hKey);


    //
    // Read the event text strings from the resource file.
    //

    hResource = (HMODULE)LoadLibrary(TEXT("lsasrv.dll"));
    if(hResource == NULL) 
    {
        return(FALSE);
    }

    SchGetMessageString(hResource,
                        SSLEVENTTEXT_CLIENT,
                        &pszClientString);

    SchGetMessageString(hResource,
                        SSLEVENTTEXT_SERVER,
                        &pszServerString);

    FreeLibrary(hResource);

    return(TRUE);
}


//+---------------------------------------------------------------------------
//
//  Function:   SchReportEvent
//
//  Synopsis:   Reports an event to the event log
//
//  Arguments:  [EventType]       -- EventType (ERROR, WARNING, etc.)
//              [EventId]         -- Event ID
//              [SizeOfRawData]   -- Size of raw data
//              [RawData]         -- Raw data
//              [NumberOfStrings] -- number of strings
//              ...               -- PWSTRs to string data
//
//  History:    03-05-99   jbanes    Created
//
//  Notes:
//
//----------------------------------------------------------------------------
DWORD
SchReportEvent(
    IN DWORD LogLevel,
    IN DWORD EventType,
    IN DWORD EventId,
    IN DWORD Category,
    IN DWORD SizeOfRawData,
    IN PVOID RawData,
    IN DWORD NumberOfStrings,
    ...
    )
{
    va_list arglist;
    ULONG i;
    PWSTR Strings[ MAX_EVENT_STRINGS ];
    PSTR StringsA[ MAX_EVENT_STRINGS ];
    DWORD Status;
    BOOL fDiscardDuplicates = TRUE;
    BOOL fSuccess;


    //
    // Is this event supposed to be logged?
    //

    if ((g_dwEventLogging & LogLevel) == 0)
    {
        return ERROR_SUCCESS;
    }

    
    //
    // Open the event log if necessary. 
    //

    if(g_dwEventLogging == DEFAULT_EVENT_LOGGING_SETTING)
    {
        // Only log identical event once per hour.
        if(g_hDiscardDupEventLog == NULL)
        {
            g_hDiscardDupEventLog = NetpEventlogOpen(EventSourceName, 60000*60);
            if(g_hDiscardDupEventLog == NULL)
            {
                Status = GetLastError();
                DebugLog((DEB_ERROR, "Could not open duplicate discard event log, error %d\n", Status));
                return Status;
            }
        }
    }
    else
    {
        // Log all events.
        if(g_hEventLog == NULL)
        {
            g_hEventLog = RegisterEventSource(NULL, EventSourceName);
            if(g_hEventLog == NULL)
            {
                Status = GetLastError();
                DebugLog((DEB_ERROR, "Could not open duplicate discard event log, error %d\n", Status));
                return Status;
            }
        }

        fDiscardDuplicates = FALSE;
    }

    
    //
    // Look at the strings, if they were provided
    //
    va_start( arglist, NumberOfStrings );

    if (NumberOfStrings > MAX_EVENT_STRINGS) {
        NumberOfStrings = MAX_EVENT_STRINGS;
    }

    for (i=0; i<NumberOfStrings; i++) 
    {
        Strings[ i ] = va_arg( arglist, PWSTR );
    }


    //
    // Report the event to the eventlog service
    //

    if(fDiscardDuplicates)
    {
        fSuccess = NetpEventlogWriteEx(   
                        g_hDiscardDupEventLog,
                        (WORD) EventType,
                        (WORD) Category,
                        EventId,
                        (WORD)NumberOfStrings,
                        SizeOfRawData,
                        Strings,
                        RawData);
    }
    else
    {
        fSuccess = ReportEvent(   
                        g_hEventLog,
                        (WORD) EventType,
                        (WORD) Category,
                        EventId,
                        NULL,
                        (WORD)NumberOfStrings,
                        SizeOfRawData,
                        Strings,
                        RawData);
    }

    if(!fSuccess)
    {
        Status = GetLastError();
        DebugLog((DEB_ERROR,  "ReportEvent( %u ) failed - %u\n", EventId, Status));
    }
    else
    {
        Status = ERROR_SUCCESS;
    }

    return Status;
}

void
SchShutdownEvents(void)
{
    if(g_hDiscardDupEventLog != NULL)
    {
        NetpEventlogClose(g_hDiscardDupEventLog);
        g_hDiscardDupEventLog = NULL;
    }
    if(g_hEventLog != NULL)
    {
        DeregisterEventSource(g_hEventLog);
        g_hEventLog = NULL;
    }

    if(pszClientString)
    {
        LocalFree(pszClientString);
        pszClientString = NULL;
    }
    if(pszServerString)
    {
        LocalFree(pszServerString);
        pszServerString = NULL;
    }
}


void
LogSchannelStartedEvent(void)
{    
    SchReportEvent( DEB_TRACE,
                    EVENTLOG_INFORMATION_TYPE,
                    SSLEVENT_SCHANNEL_STARTED,
                    0,
                    0,
                    NULL,
                    0,
                    NULL );
}

void
LogGlobalAcquireContextFailedEvent(
    LPWSTR pwszName,
    DWORD Status)
{
    WCHAR wszStatus[20];

    _ltow(Status, wszStatus, 16);

    SchReportEvent( DEB_ERROR,
                    EVENTLOG_ERROR_TYPE,
                    SSLEVENT_GLOBAL_ACQUIRE_CONTEXT_FAILED,
                    0,
                    0,
                    NULL,
                    2,
                    pwszName,
                    wszStatus);
}

void
LogCreateCredEvent(
    DWORD dwProtocol, 
    PLSA_SCHANNEL_CRED pSchannelCred)
{
    SchReportEvent(DEB_TRACE,
                   EVENTLOG_INFORMATION_TYPE,
                   SSLEVENT_CREATE_CRED,
                   0,
                   sizeof(SCHANNEL_CRED),
                   pSchannelCred,
                   1,
                   (dwProtocol & SP_PROT_SERVERS) ? pszServerString : pszClientString);
}

void
LogCredPropertiesEvent(
    DWORD dwProtocol,
    PCRYPT_KEY_PROV_INFO pProvInfo,
    PCCERT_CONTEXT pCertContext)
{
    WCHAR wszType[20];
    WCHAR wszFlags[20];
    LPWSTR pwszKeySpec;

    if(!(g_dwEventLogging & DEB_TRACE))
    {
        return;
    }

    _ltow(pProvInfo->dwProvType, wszType,    10);
    _ltow(pProvInfo->dwFlags,    wszFlags,   16);

    switch(pProvInfo->dwKeySpec)
    {
    case AT_KEYEXCHANGE:
        pwszKeySpec = L"key exchange";
        break;
    case AT_SIGNATURE:
        pwszKeySpec = L"signature";
        break;
    default:
        pwszKeySpec = L"unknown";
    }

    SchReportEvent( DEB_TRACE,
                    EVENTLOG_INFORMATION_TYPE,
                    SSLEVENT_CRED_PROPERTIES,
                    0,
                    pCertContext->cbCertEncoded,
                    pCertContext->pbCertEncoded,
                    6,
                    (dwProtocol & SP_PROT_SERVERS) ? pszServerString : pszClientString,
                    pProvInfo->pwszProvName,
                    wszType,
                    pProvInfo->pwszContainerName,
                    pwszKeySpec,
                    wszFlags);
}

void
LogNoPrivateKeyEvent(
    DWORD dwProtocol)
{
    SchReportEvent( DEB_ERROR,
                    EVENTLOG_ERROR_TYPE,
                    SSLEVENT_NO_PRIVATE_KEY,
                    0,
                    0,
                    NULL,
                    1,
                    (dwProtocol & SP_PROT_SERVERS) ? pszServerString : pszClientString);
}

void
LogCredAcquireContextFailedEvent(
    DWORD dwProtocol, 
    DWORD Status)
{
    WCHAR wszStatus[20];

    _ltow(Status, wszStatus, 16);

    SchReportEvent( DEB_ERROR,
                    EVENTLOG_ERROR_TYPE,
                    SSLEVENT_CRED_ACQUIRE_CONTEXT_FAILED,
                    0,
                    0,
                    NULL,
                    2,
                    (dwProtocol & SP_PROT_SERVERS) ? pszServerString : pszClientString,
                    wszStatus);
}

void
LogCreateCredFailedEvent(
    DWORD dwProtocol)
{
    SchReportEvent(DEB_ERROR,
                   EVENTLOG_ERROR_TYPE,
                   SSLEVENT_CREATE_CRED_FAILED,
                   0,
                   0,
                   NULL,
                   1,
                   (dwProtocol & SP_PROT_SERVERS) ? pszServerString : pszClientString);
}

void
LogNoDefaultServerCredEvent(void)
{
    SchReportEvent(DEB_ERROR,
                   EVENTLOG_WARNING_TYPE,
                   SSLEVENT_NO_DEFAULT_SERVER_CRED,
                   0,
                   0,
                   NULL,
                   0,
                   NULL);
}

           
void
LogNoCiphersSupportedEvent(void)
{
    SchReportEvent(DEB_ERROR,
                   EVENTLOG_ERROR_TYPE,
                   SSLEVENT_NO_CIPHERS_SUPPORTED,
                   0,
                   0,
                   NULL,
                   0,
                   NULL);
}

void
LogCipherMismatchEvent(void)
{
    SchReportEvent(DEB_ERROR,
                   EVENTLOG_ERROR_TYPE,
                   SSLEVENT_CIPHER_MISMATCH,
                   0,
                   0,
                   NULL,
                   0,
                   NULL);
}

void
LogNoClientCertFoundEvent(void)
{
    SchReportEvent(DEB_WARN,
                   EVENTLOG_WARNING_TYPE,
                   SSLEVENT_NO_CLIENT_CERT_FOUND,
                   0,
                   0,
                   NULL,
                   0,
                   NULL);
}

void
LogBogusServerCertEvent(
    PCCERT_CONTEXT pCertContext,
    LPWSTR pwszServerName,
    DWORD Status)
{
    WCHAR wszStatus[20];

    if(!(g_dwEventLogging & DEB_ERROR))
    {
        return;
    }

    switch(Status)
    {
    case SEC_E_CERT_EXPIRED:
        SchReportEvent( DEB_ERROR,
                        EVENTLOG_ERROR_TYPE,
                        SSLEVENT_EXPIRED_SERVER_CERT,
                        0,
                        pCertContext->cbCertEncoded,
                        pCertContext->pbCertEncoded,
                        0,
                        NULL);
        break;

    case SEC_E_UNTRUSTED_ROOT:
        SchReportEvent( DEB_ERROR,
                        EVENTLOG_ERROR_TYPE,
                        SSLEVENT_UNTRUSTED_SERVER_CERT,
                        0,
                        pCertContext->cbCertEncoded,
                        pCertContext->pbCertEncoded,
                        0,
                        NULL);
        break;

    case CRYPT_E_REVOKED:
        SchReportEvent( DEB_ERROR,
                        EVENTLOG_ERROR_TYPE,
                        SSLEVENT_REVOKED_SERVER_CERT,
                        0,
                        pCertContext->cbCertEncoded,
                        pCertContext->pbCertEncoded,
                        0,
                        NULL);
        break;

    case SEC_E_WRONG_PRINCIPAL:
        SchReportEvent( DEB_ERROR,
                        EVENTLOG_ERROR_TYPE,
                        SSLEVENT_NAME_MISMATCHED_SERVER_CERT,
                        0,
                        pCertContext->cbCertEncoded,
                        pCertContext->pbCertEncoded,
                        1,
                        pwszServerName);
        break;
       
    default:
        _ltow(Status, wszStatus, 16);

        SchReportEvent( DEB_ERROR,
                        EVENTLOG_ERROR_TYPE,
                        SSLEVENT_BOGUS_SERVER_CERT,
                        0,
                        pCertContext->cbCertEncoded,
                        pCertContext->pbCertEncoded,
                        1,
                        wszStatus);
    }
}

void
LogBogusClientCertEvent(
    PCCERT_CONTEXT pCertContext,
    DWORD Status)
{
    WCHAR wszStatus[20];

    if(!(g_dwEventLogging & DEB_WARN))
    {
        return;
    }

    _ltow(Status, wszStatus, 16);

    SchReportEvent( DEB_WARN,
                    EVENTLOG_WARNING_TYPE,
                    SSLEVENT_BOGUS_CLIENT_CERT,
                    0,
                    pCertContext->cbCertEncoded,
                    pCertContext->pbCertEncoded,
                    1,
                    wszStatus);
}

void
LogFastMappingFailureEvent(
    PCCERT_CONTEXT pCertContext,
    DWORD Status)
{
    WCHAR wszStatus[20];

    if(!(g_dwEventLogging & DEB_WARN))
    {
        return;
    }

    _ltow(Status, wszStatus, 16);

    SchReportEvent( DEB_WARN,
                    EVENTLOG_WARNING_TYPE,
                    SSLEVENT_FAST_MAPPING_FAILURE,
                    0,
                    pCertContext->cbCertEncoded,
                    pCertContext->pbCertEncoded,
                    1,
                    wszStatus);
}

void
LogCertMappingFailureEvent(
    DWORD Status)
{
    WCHAR wszStatus[20];

    if(!(g_dwEventLogging & DEB_WARN))
    {
        return;
    }

    _ltow(Status, wszStatus, 16);

    SchReportEvent( DEB_WARN,
                    EVENTLOG_WARNING_TYPE,
                    SSLEVENT_CERT_MAPPING_FAILURE,
                    0,
                    0,
                    NULL,
                    1,
                    wszStatus);
}

void
LogHandshakeInfoEvent(
    DWORD dwProtocol,
    PCipherInfo pCipherInfo,
    PHashInfo pHashInfo,
    PKeyExchangeInfo pExchangeInfo,
    DWORD dwExchangeStrength)
{
    WCHAR wszCipherStrength[20];
    WCHAR wszExchangeStrength[20];
    LPWSTR pwszProtocol;
    LPWSTR pwszCipher;
    LPWSTR pwszHash;
    LPWSTR pwszExchange;

    if(!(g_dwEventLogging & DEB_TRACE))
    {
        return;
    }

    switch(dwProtocol)
    {
    case SP_PROT_PCT1_SERVER:
    case SP_PROT_PCT1_CLIENT:
        pwszProtocol = L"PCT";
        break;
    case SP_PROT_SSL2_SERVER:
    case SP_PROT_SSL2_CLIENT:
        pwszProtocol = L"SSL 2.0";
        break;
    case SP_PROT_SSL3_SERVER:
    case SP_PROT_SSL3_CLIENT:
        pwszProtocol = L"SSL 3.0";
        break;
    case SP_PROT_TLS1_SERVER:
    case SP_PROT_TLS1_CLIENT:
        pwszProtocol = L"TLS (SSL 3.1)";
        break;
    default:
        pwszProtocol = L"unknown";
    }

    switch(pCipherInfo->aiCipher)
    {
    case CALG_RC4:
        pwszCipher = L"RC4";
        break;
    case CALG_3DES:
        pwszCipher = L"Triple-DES";
        break;
    case CALG_RC2:
        pwszCipher = L"RC2";
        break;
    case CALG_DES:
        pwszCipher = L"DES";
        break;
    case CALG_SKIPJACK:
        pwszCipher = L"Skipjack";
        break;
    default:
        pwszCipher = L"unknown";
    }

    _ltow(pCipherInfo->dwStrength, wszCipherStrength, 10);

    switch(pHashInfo->aiHash)
    {
    case CALG_MD5:
        pwszHash = L"MD5";
        break;
    case CALG_SHA:
        pwszHash = L"SHA";
        break;
    default:
        pwszHash = L"unknown";
    }

    switch(pExchangeInfo->aiExch)
    {
    case CALG_RSA_SIGN:
    case CALG_RSA_KEYX:
        pwszExchange = L"RSA";
        break;
    case CALG_KEA_KEYX:
        pwszExchange = L"KEA";
        break;
    case CALG_DH_EPHEM:
        pwszExchange = L"Ephemeral DH";
        break;
    default:
        pwszExchange = L"unknown";
    }

    _ltow(dwExchangeStrength, wszExchangeStrength, 10);

    SchReportEvent( DEB_TRACE,
                    EVENTLOG_INFORMATION_TYPE,
                    SSLEVENT_HANDSHAKE_INFO,
                    0,
                    0,
                    NULL,
                    7,
                    (dwProtocol & SP_PROT_SERVERS) ? pszServerString : pszClientString,
                    pwszProtocol,
                    pwszCipher,
                    wszCipherStrength,
                    pwszHash,
                    pwszExchange,
                    wszExchangeStrength);
}

NTSTATUS
SchGetMessageString(
    LPVOID   Resource,
    DWORD    Index,
    LPWSTR * pRetString)
{
    DWORD Length;

    *pRetString = NULL;

    Length = FormatMessage(FORMAT_MESSAGE_FROM_HMODULE |
                           FORMAT_MESSAGE_ALLOCATE_BUFFER,
                           Resource,
                           Index,
                           0,                 // Use caller's language
                           (LPWSTR)pRetString,
                           0,
                           NULL);

    if(Length == 0 || *pRetString == NULL)
    {
        return(STATUS_RESOURCE_DATA_NOT_FOUND);
    }

    //
    // Note that we are retrieving a message from a message file.
    // This message will have a cr/lf tacked on the end of it
    // (0x0d 0x0a) that we don't want to be part of our returned
    // strings.  However, we do need to null terminate our string
    // so we will convert the 0x0d into a null terminator.
    //
    // Also note that FormatMessage() returns a character count,
    // not a byte count.  So, we have to do some adjusting to make
    // the string lengths correct.
    //

    ASSERT(Length >= 2);    // We always expect cr/lf on our strings

    //
    // Adjust character count
    //

    Length -=  1; // For the lf - we'll convert the cr.

    //
    // Set null terminator
    //

    (*pRetString)[Length - 1] = 0;

    return(STATUS_SUCCESS);
}