/*++

Copyright (c) 1994  Microsoft Corporation

Module Name:

    dhcp.c

Abstract:

    This file contains utility functions.

Author:

    Madan Appiah (madana) 7-Dec-1993.

Environment:

    User Mode - Win32

Revision History:

--*/

#include "precomp.h"
#include "dhcpglobal.h"
#include <dhcploc.h>
#include <dhcppro.h>

#define  MESSAGE_BOX_WIDTH_IN_CHARS 65

typedef struct _POPUP_THREAD_PARAM {
    LPWSTR Title;
    LPWSTR Message;
    ULONG  Flags;
} POPUP_THREAD_PARAM, *LPPOPUP_THREAD_PARAM;

POPUP_THREAD_PARAM PopupThreadParam = { NULL, NULL, 0 };


DWORD
DoPopup(
    PVOID Buffer
    )
/*++

Routine Description:

    This function pops up a message to the user.  It must run it's own
    thread.  When the user acknowledge the popup, the thread
    deallocates the message buffer and returns.

Arguments:

    Buffer - A pointer to a NULL terminated message buffer.

Return Values:

    Always returns 0
    
--*/
{
    DWORD Result;
    LPPOPUP_THREAD_PARAM Params = Buffer;

    Result = MessageBox(
        NULL, // no owner
        Params->Message, 
        Params->Title,
        ( MB_OK | Params->Flags |
          MB_SERVICE_NOTIFICATION |
          MB_SYSTEMMODAL |
          MB_SETFOREGROUND |
          MB_DEFAULT_DESKTOP_ONLY
            )
        );


    LOCK_POPUP();

    if( Params->Message != NULL ) {
        LocalFree( Params->Message );
        Params->Message = NULL;
    }

    if( Params->Title != NULL ) {
        LocalFree( Params->Title );
        Params->Title = NULL;
    }

    //
    // close the global handle, so that we will not consume this
    // thread resource until another popup.
    //

    CloseHandle( DhcpGlobalMsgPopupThreadHandle );
    DhcpGlobalMsgPopupThreadHandle = NULL;

    UNLOCK_POPUP();

    //
    // Always return 0
    //
    
    return 0;
}


DWORD
DisplayUserMessage(
    IN PDHCP_CONTEXT DhcpContext,
    IN DWORD MessageId,
    IN DHCP_IP_ADDRESS IpAddress
)
/*++

Routine Description:

    This function starts a new thread to display a message box.

    N.B.  If a thread already exists which is waiting for user input
    on a message box, then this routine does not create another
    thread.

Arguments:

    DhcpContext -- the context to display messages for
    
    MessageId - The ID of the message to display.
        (The actual message string is obtained from the dhcp module).

    IpAddress - Ip address involved.

--*/
{
    DWORD ThreadID, TitleLength, MsgLength, Flags;
    LPWSTR Title = NULL, Message = NULL;

    switch(MessageId) {

    case MESSAGE_FAILED_TO_OBTAIN_LEASE:
        Flags = MB_ICONSTOP;
        break;

    case MESSAGE_SUCCESSFUL_LEASE :
        Flags = MB_ICONINFORMATION;
        break;

    default:
        DhcpAssert(FALSE);
        Flags = MB_ICONSTOP;
        break;
    }

    LOCK_POPUP();

    //
    // if we are asked to display no message popup, simply return.
    //

    if ( DhcpGlobalDisplayPopup == FALSE ) {
        goto Cleanup;
    }

    //
    // if the message popup thread handle is non-null, check to see
    // the thread is still running, if so don't display another popup,
    // otherwise close the last popup handle and create another popup 
    // thread for new message.
    //

    if( DhcpGlobalMsgPopupThreadHandle != NULL ) {
        DWORD WaitStatus;

        //
        // Time out immediately if the thread is still running.
        //

        WaitStatus = WaitForSingleObject(
            DhcpGlobalMsgPopupThreadHandle,
            0 );
        
        if ( WaitStatus == WAIT_TIMEOUT ) {
            goto Cleanup;

        } else if ( WaitStatus == 0 ) {

            //
            // This shouldn't be a case, because we close this handle at
            // the end of popup thread.
            //

            DhcpAssert( WaitStatus == 0 );

            CloseHandle( DhcpGlobalMsgPopupThreadHandle );
            DhcpGlobalMsgPopupThreadHandle = NULL;

        } else {
            DhcpPrint((
                DEBUG_ERRORS,
                    "Cannot WaitFor message popup thread: %ld\n",
                        WaitStatus ));
            goto Cleanup;
        }
    }


    MsgLength = FormatMessage(
        FORMAT_MESSAGE_FROM_HMODULE
        | FORMAT_MESSAGE_ARGUMENT_ARRAY 
        | FORMAT_MESSAGE_ALLOCATE_BUFFER
        | MESSAGE_BOX_WIDTH_IN_CHARS,
        (LPVOID)DhcpGlobalMessageFileHandle,
        MessageId,
        0,  // language id.
        (LPWSTR)&Message, // return buffer place holder.
        0,  // minimum buffer size to allocate.
        NULL   // No Params
    );

    if ( MsgLength == 0) {
        DhcpPrint(( DEBUG_ERRORS,
            "FormatMessage failed, err = %ld.\n", GetLastError()));
        goto Cleanup;
    }

    DhcpAssert( Message != NULL );
    DhcpAssert( (wcslen(Message)) == MsgLength );

    //
    // get message box title.
    //

    TitleLength = FormatMessage(
        FORMAT_MESSAGE_FROM_HMODULE |
        FORMAT_MESSAGE_ALLOCATE_BUFFER,
        (LPVOID)DhcpGlobalMessageFileHandle,
        MESSAGE_POPUP_TITLE,
        0, // language id.
        (LPWSTR)&Title,  // return buffer place holder.
        0, // minimum buffer size to allocate.
        NULL  // insert strings.
    );

    if ( TitleLength == 0) {
        DhcpPrint(( DEBUG_ERRORS,
            "FormatMessage to Message box Title failed, err = %ld.\n",
                GetLastError()));
        goto Cleanup;
    }

    DhcpAssert( Title != NULL );
    DhcpAssert( (wcslen(Title)) == TitleLength );

    PopupThreadParam.Title = Title;
    PopupThreadParam.Message = Message;
    PopupThreadParam.Flags = Flags;


    //
    // Create a thread, to display a message box to the user.  We need
    // a new thread because MessageBox() blocks until the user clicks
    // on the OK button, and we can't block this thread.
    //
    // DoPopup frees the buffer.
    //

    DhcpGlobalMsgPopupThreadHandle = CreateThread(
        NULL,    // no security.
        0,       // default stack size.
        DoPopup, // entry point.
        (PVOID)&PopupThreadParam,
        0, 
        &ThreadID
    );

    if ( DhcpGlobalMsgPopupThreadHandle == NULL ) {
        DhcpPrint((
            DEBUG_ERRORS,
            "DisplayUserMessage:  Could not create thread, err = %ld.\n",
            GetLastError() ));
    }

Cleanup:

    UNLOCK_POPUP();

    return 0;
}

VOID
DhcpLogEvent(
    IN PDHCP_CONTEXT DhcpContext, OPTIONAL
    IN DWORD EventNumber,
    IN DWORD ErrorCode OPTIONAL
)
/*++

Routine Description:

    This functions formats and writes an event log entry.

Arguments:

    DhcpContext - The context for the event. Optional parameter.

    EventNumber - The event to log.

    ErrorCode - Windows Error code to record. Optional parameter.

--*/
{
    LPWSTR HWAddressBuffer = NULL;
    LPWSTR IPAddressBuffer = NULL;
    LPWSTR IPAddressBuffer2 = NULL;
    CHAR ErrorCodeOemStringBuf[32 + 1];
    WCHAR ErrorCodeStringBuf[32 + 1];
    LPWSTR ErrorCodeString = NULL;
    LPWSTR Strings[10];
    DHCP_IP_ADDRESS IpAddr;

    if( DhcpContext != NULL ) {

        if( EVENT_NACK_LEASE == EventNumber ) {
            IpAddr = DhcpContext->NackedIpAddress;
        } if( EVENT_ADDRESS_CONFLICT == EventNumber ) {
            IpAddr = DhcpContext->ConflictAddress;
        } else {
            IpAddr = DhcpContext->IpAddress;
        }
        
        HWAddressBuffer = DhcpAllocateMemory(
            (DhcpContext->HardwareAddressLength * 2 + 1) *
            sizeof(WCHAR)
            );

        if( HWAddressBuffer == NULL ) {
            DhcpPrint(( DEBUG_MISC, "Out of memory." ));
            goto Cleanup;
        }

        DhcpHexToString(
            HWAddressBuffer,
            DhcpContext->HardwareAddress,
            DhcpContext->HardwareAddressLength
            );

        HWAddressBuffer[DhcpContext->HardwareAddressLength * 2] = '\0';

        IPAddressBuffer = DhcpOemToUnicode(
            inet_ntoa( *(struct in_addr *)&IpAddr ),
            NULL
            );

        if( IPAddressBuffer == NULL ) {
            DhcpPrint(( DEBUG_MISC, "Out of memory." ));
            goto Cleanup;
        }

        if( EVENT_NACK_LEASE == EventNumber ) {
            IPAddressBuffer2 = DhcpOemToUnicode(
                inet_ntoa( *(struct in_addr *)&DhcpContext->DhcpServerAddress ),
                NULL
            );

            if( NULL == IPAddressBuffer2 ) goto Cleanup;
        }
    }

    strcpy( ErrorCodeOemStringBuf, "%%" );
    _ultoa( ErrorCode, ErrorCodeOemStringBuf + 2, 10 );

    ErrorCodeString = DhcpOemToUnicode(
                        ErrorCodeOemStringBuf,
                        ErrorCodeStringBuf );

    //
    // Log an event
    //

    switch ( EventNumber ) {

    case EVENT_LEASE_TERMINATED:

        DhcpAssert( HWAddressBuffer != NULL );
        DhcpAssert( IPAddressBuffer != NULL );

        Strings[0] = HWAddressBuffer;
        Strings[1] = IPAddressBuffer;

        DhcpReportEventW(
            DHCP_EVENT_CLIENT,
            EVENT_LEASE_TERMINATED,
            EVENTLOG_ERROR_TYPE,
            2,
            0,
            Strings,
            NULL );

        break;

    case EVENT_FAILED_TO_OBTAIN_LEASE:

        DhcpAssert( HWAddressBuffer != NULL );
        DhcpAssert( ErrorCodeString != NULL );

        Strings[0] = HWAddressBuffer;
        Strings[1] = ErrorCodeString;

        DhcpReportEventW(
            DHCP_EVENT_CLIENT,
            EVENT_FAILED_TO_OBTAIN_LEASE,
            EVENTLOG_ERROR_TYPE,
            2,
            sizeof(ErrorCode),
            Strings,
            &ErrorCode );

        break;

    case EVENT_NACK_LEASE:

        DhcpAssert( HWAddressBuffer != NULL );
        DhcpAssert( IPAddressBuffer != NULL );
        DhcpAssert( IPAddressBuffer2 != NULL );

        Strings[0] = IPAddressBuffer;
        Strings[1] = HWAddressBuffer;
        Strings[2] = IPAddressBuffer2;

        DhcpReportEventW(
            DHCP_EVENT_CLIENT,
            EVENT_NACK_LEASE,
            EVENTLOG_ERROR_TYPE,
            3,
            0,
            Strings,
            NULL );

        break;

    case EVENT_ADDRESS_CONFLICT:
        DhcpAssert( IPAddressBuffer != NULL );
        DhcpAssert( HWAddressBuffer != NULL );

        Strings[0] = IPAddressBuffer;
        Strings[1] = HWAddressBuffer;

        DhcpReportEventW(
            DHCP_EVENT_CLIENT,
            EVENT_ADDRESS_CONFLICT,
            EVENTLOG_WARNING_TYPE,
            2,
            0,
            Strings,
            NULL );
        break;

    case EVENT_IPAUTOCONFIGURATION_FAILED:
        DhcpAssert( HWAddressBuffer != NULL );
        DhcpAssert( ErrorCodeString != NULL );

        Strings[0] = HWAddressBuffer;
        Strings[1] = ErrorCodeString;

        DhcpReportEventW(
            DHCP_EVENT_CLIENT,
            EVENT_IPAUTOCONFIGURATION_FAILED,
            EVENTLOG_WARNING_TYPE,
            2,
            sizeof(ErrorCode),
            Strings,
            &ErrorCode );

        break;

     case EVENT_FAILED_TO_RENEW:

        // The 'timeout' event should be logged only if there is no PPP
        // adapter up. It is so because having a PPP adapter means the
        // routes are hijacked, hence renewals up to T2 are expected to
        // fail.
        if (ErrorCode != ERROR_SEM_TIMEOUT ||
            DhcpGlobalNdisWanAdaptersCount == 0 ||
            time(NULL) >= DhcpContext->T2Time)
        {
            DhcpAssert( HWAddressBuffer != NULL );
            DhcpAssert( ErrorCodeString != NULL );

            Strings[0] = HWAddressBuffer;
            Strings[1] = ErrorCodeString;

            DhcpReportEventW(
                DHCP_EVENT_CLIENT,
                EVENT_FAILED_TO_RENEW,
                EVENTLOG_WARNING_TYPE,
                2,
                sizeof(ErrorCode),
                Strings,
                &ErrorCode );
        }

        break;

    case EVENT_DHCP_SHUTDOWN:

        DhcpAssert( ErrorCodeString != NULL );

        Strings[0] = ErrorCodeString;

        DhcpReportEventW(
            DHCP_EVENT_CLIENT,
            EVENT_DHCP_SHUTDOWN,
            EVENTLOG_WARNING_TYPE,
            1,
            sizeof(ErrorCode),
            Strings,
            &ErrorCode );

        break;

    case EVENT_IPAUTOCONFIGURATION_SUCCEEDED :

        Strings[0] = HWAddressBuffer;
        Strings[1] = IPAddressBuffer;

        DhcpReportEventW(
            DHCP_EVENT_CLIENT,
            EVENT_IPAUTOCONFIGURATION_SUCCEEDED,
            EVENTLOG_WARNING_TYPE,
            2,
            sizeof(ErrorCode),
            Strings,
            &ErrorCode
        );
        break;

    case EVENT_COULD_NOT_INITIALISE_INTERFACE :

        DhcpAssert( NULL != ErrorCodeString);
        Strings[0] = ErrorCodeString;
        DhcpReportEventW(
            DHCP_EVENT_CLIENT,
            EVENT_COULD_NOT_INITIALISE_INTERFACE,
            EVENTLOG_ERROR_TYPE,
            1,
            sizeof(ErrorCode),
            Strings,
            &ErrorCode
        );

        break;

    case EVENT_NET_ERROR:
        DhcpAssert( NULL != ErrorCodeString);
        Strings[0] = ErrorCodeString;
        DhcpReportEventW(
            DHCP_EVENT_CLIENT,
            EVENT_NET_ERROR,
            EVENTLOG_WARNING_TYPE,
            1,
            sizeof(ErrorCode),
            Strings,
            &ErrorCode
        );
        break;

    default:

        DhcpPrint(( DEBUG_MISC, "Unknown event." ));
        break;
   }

Cleanup:

    if( HWAddressBuffer != NULL ) {
        DhcpFreeMemory( HWAddressBuffer );
    }

    if( IPAddressBuffer != NULL ) {
        DhcpFreeMemory( IPAddressBuffer );
    }

    if( IPAddressBuffer2 != NULL ) {
        DhcpFreeMemory( IPAddressBuffer2 );
    }

}

#if DBG

VOID
DhcpPrintRoutine(
    IN DWORD DebugFlag,
    IN LPSTR Format,
    ...
    )

{

#define MAX_PRINTF_LEN 1024        // Arbitrary.

    va_list arglist;
    char OutputBuffer[MAX_PRINTF_LEN];
    ULONG length;
    static BeginningOfLine = TRUE;
    LPSTR Text;

    //
    // If we aren't debugging this functionality, just return.
    //

    if ( DebugFlag != 0 && (DhcpGlobalDebugFlag & DebugFlag) == 0 ) {
        return;
    }

    //
    // vsprintf isn't multithreaded + we don't want to intermingle output
    // from different threads.
    //

    // EnterCriticalSection( &DhcpGlobalDebugFileCritSect );

    length = 0;

    //
    // Handle the beginning of a new line.
    //
    //

    if ( BeginningOfLine ) {

        length += (ULONG) sprintf( &OutputBuffer[length], "[Dhcp] " );

        //
        // Put the timestamp at the begining of the line.
        //
        IF_DEBUG( TIMESTAMP ) {
            SYSTEMTIME SystemTime;
            GetLocalTime( &SystemTime );
            length += (ULONG) sprintf( &OutputBuffer[length],
                                  "%02u/%02u %02u:%02u:%02u ",
                                  SystemTime.wMonth,
                                  SystemTime.wDay,
                                  SystemTime.wHour,
                                  SystemTime.wMinute,
                                  SystemTime.wSecond );
        }

        //
        // Indicate the type of message on the line
        //
        switch (DebugFlag) {
        case DEBUG_ERRORS:
            Text = "ERRR";
            break;

        case DEBUG_PROTOCOL:
            Text = "PROT";
            break;

        case DEBUG_LEASE:
            Text = "LEAS";
            break;

        case DEBUG_PROTOCOL_DUMP:
            Text = "DUMP";
            break;

        case DEBUG_MISC:
            Text = "MISC";
            break;

        default:
            Text = "DHCP";
            break;
        }

        if ( Text != NULL ) {
            length += (ULONG) sprintf( &OutputBuffer[length], "[%s] ", Text );
        }
    }

    //
    // Put a the information requested by the caller onto the line
    //

    va_start(arglist, Format);

    length += (ULONG) vsprintf(&OutputBuffer[length], Format, arglist);
    BeginningOfLine = (length > 0 && OutputBuffer[length-1] == '\n' );

    va_end(arglist);

    DhcpAssert(length <= MAX_PRINTF_LEN);


    //
    // Output to the debug terminal,
    //

    if (NULL == DhcpGlobalDebugFile) {
        (void) DbgPrint( (PCH) OutputBuffer);
    } else {

        //
        // Note: other process can still write to the log file. This should be OK since
        // only the Dhcp client service is supposed to write to the log file.
        //
        EnterCriticalSection( &DhcpGlobalDebugFileCritSect );
        SetFilePointer(DhcpGlobalDebugFile, 0, NULL, FILE_END);
        WriteFile(DhcpGlobalDebugFile, OutputBuffer, length, &length, NULL);
        LeaveCriticalSection( &DhcpGlobalDebugFileCritSect );
    }

    // LeaveCriticalSection( &DhcpGlobalDebugFileCritSect );

}

#endif // DBG


PDHCP_CONTEXT
FindDhcpContextOnNicList(
    IN LPCWSTR AdapterName, OPTIONAL
    IN DWORD InterfaceContext
    )
/*++

Routine Description:

    This function finds the DHCP_CONTEXT for the specified
    adapter name on the Nic list.

    This function must be called with LOCK_RENEW_LIST().

Arguments:

    AdapterName - name of the adapter.
    HardwareAddress - The hardware address to look for.

Return Value:

    A pointer to the desired DHCP work context.
    NULL - If the specified work context block cannot be found.

--*/
{
    PLIST_ENTRY listEntry;
    PDHCP_CONTEXT dhcpContext;
    PLOCAL_CONTEXT_INFO LocalInfo;

    listEntry = DhcpGlobalNICList.Flink;
    while ( listEntry != &DhcpGlobalNICList ) {
        dhcpContext = CONTAINING_RECORD( listEntry, DHCP_CONTEXT, NicListEntry );

        LocalInfo = dhcpContext->LocalInformation;
        if ( AdapterName ) {
            if( _wcsicmp( LocalInfo->AdapterName, AdapterName ) == 0 ) {
                return( dhcpContext );
            }

        } else {
            if( LocalInfo->IpInterfaceContext == InterfaceContext ) {
                return( dhcpContext );
            }
        }

        listEntry = listEntry->Flink;
    }

    return( NULL );
}

//
// End of file
//