/*++

Copyright (c) 2000, Microsoft Corporation

Module Name:

    eldeviceio.c

Abstract:

    This module contains implementations for media-management and device I/O.
    The routines declared here operate asynchronously on the handles 
    associated with an I/O completion port opened on the ndis uio driver. 

Revision History:

    sachins, Apr 23 2000, Created

--*/

#include "pcheapol.h"
#pragma hdrstop

// NDISUIO constants

CHAR            NdisuioDevice[] = "\\\\.\\\\Ndisuio";
CHAR *          pNdisuioDevice = &NdisuioDevice[0];

// TEST globals
PVOID g_QueryBinding;
PVOID g_TempBuf;
PVOID g_ItfBuffer;
PVOID g_Buf;
DWORD g_ItfBufferSize;
DWORD g_BreakAt;


extern 
VOID 
ElUserLogonDetection (
        PVOID   pvContext
        );


//
// ElMediaInit
// 
// Description:
//
// Called on EAPOL service startup to initialize all the media related events 
// and callback functions
// 
//
// Arguments:
//
// Return Values:
//

DWORD
ElMediaInit (
        )
{
    DWORD       dwIndex = 0;
    DWORD       dwRetCode = NO_ERROR;

    TRACE0 (INIT, "ElMediaInit: Entered");

    do 
    {
        // --ft:12/04/00: Initialize locks as the first thing to do. Otherwise, EAPOL registers
        // with WMI and if a notification comes in before getting to initialize the lock this can
        // result in an AV. 
        // Stress failure reported an AV on the following stack:
        // ntdll!RtlpWaitForCriticalSection+0xb1 [z:\nt\base\ntdll\resource.c @ 1631]
        // ntdll!RtlEnterCriticalSection+0x46 [Z:\nt\base\ntdll\i386\critsect.asm @ 157]
        // netman!AcquireWriteLock+0xf [z:\nt\net\config\netman\eapol\service\elsync.c @ 137]
        // netman!ElMediaSenseCallbackWorker+0x15c [z:\nt\net\config\netman\eapol\service\eldeviceio.c @ 937]
        //
        // The CRITICAL_SECTION looked like below:
        //     75e226c4  00000000 00000001 00000000 00000000
        //     75e226d4  00001990 
        // 
        // The (null) DebugInfo shows the object not being initialized successfully.
        //
        if (dwRetCode = CREATE_READ_WRITE_LOCK(&(g_ITFLock), "ITF") != NO_ERROR)
        {
            TRACE1(EAPOL, "ElMediaInit: Error (%ld) in creating g_ITFLock read-write-lock", dwRetCode);
            break;
        }
        // Initialize NLA locks
        if (dwRetCode = CREATE_READ_WRITE_LOCK(&(g_NLALock), "NLA") != NO_ERROR)
        {
            TRACE1(EAPOL, "ElMediaInit: Error (%ld) in creating g_NLALock read-write-lock", dwRetCode);
            break;
        }

        // Register for Media Sense detection of MEDIA_CONNECT and 
        // MEDIA_DISCONNECT of interfaces
        // This is done before anything else, so that no MEDIA events are lost
    
        if ((dwRetCode = ElMediaSenseRegister (TRUE)) != NO_ERROR)
        {
            TRACE1(INIT, "ElMediaInit: ElMediaSenseRegister failed with dwRetCode = %d", 
                    dwRetCode );
            break;
        }
        else
        {
            g_dwModulesStarted |= WMI_MODULE_STARTED;
            TRACE0(INIT, "ElMediaInit: ElMediaSenseRegister successful");
        }

        // Register for device notifications. We are interested in LAN 
        // interfaces coming and going. 

        if ((dwRetCode = ElDeviceNotificationRegister (TRUE)) != NO_ERROR)
        {
            TRACE1(INIT, "ElMediaInit: ElDeviceNotificationRegister failed with dwRetCode = %d", 
                    dwRetCode );
            break;
        }
        else
        {
            g_dwModulesStarted |= DEVICE_NOTIF_STARTED;
            TRACE0(INIT, "ElMediaInit: ElDeviceNotificationRegister successful");
        }

        // Initialize EAPOL structures

        if ((dwRetCode = ElInitializeEAPOL()) != NO_ERROR)
        {
            TRACE1(INIT, "ElMediaInit: ElInitializeEAPOL failed with dwRetCode = %d", 
                    dwRetCode );
            break;
        }
        else
        {
            TRACE0(INIT, "ElMediaInit: ElInitializeEAPOL successful");
            g_dwModulesStarted |= EAPOL_MODULE_STARTED;
        }
    
        // Watch and update change in global registry parameters

        if (!QueueUserWorkItem (
                    (LPTHREAD_START_ROUTINE)ElWatchGlobalRegistryParams,
                    NULL,
                    WT_EXECUTELONGFUNCTION))
        {
            dwRetCode = GetLastError();
            TRACE1 (DEVICE, "ElMediaInit: Critical error: QueueUserWorkItem failed for ElWatchGlobalRegistryParams with error %ld",
                    dwRetCode);
            break;
        }
    
        // Watch changes in EAP config params by checking on
        // ..\EAPOL\Interfaces key

        if (!QueueUserWorkItem (
                    (LPTHREAD_START_ROUTINE)ElWatchEapConfigRegistryParams,
                    NULL,
                    WT_EXECUTELONGFUNCTION 
                    ))
        {
            dwRetCode = GetLastError();
            TRACE1 (DEVICE, "ElMediaInit: Critical error: QueueUserWorkItem failed for ElWatchEapConfigRegistryParams with error %ld",
                    dwRetCode);
            break;
        }

        // Use task bar window for getting login notifications

        if (!QueueUserWorkItem (
            (LPTHREAD_START_ROUTINE)ElUserLogonDetection,
            NULL,
            WT_EXECUTELONGFUNCTION))
        {
            dwRetCode = GetLastError();
            TRACE1 (DEVICE, "Critical error: QueueUserWorkItem failed for ElUserLogonDetection with error %ld",
                    dwRetCode);
            break;
        }
    
        // Initialize interface hash bucket table
    
        g_ITFTable.pITFBuckets = (ITF_BUCKET *) MALLOC (INTF_TABLE_BUCKETS * sizeof (ITF_BUCKET));
    
        if (g_ITFTable.pITFBuckets == NULL)
        {
            TRACE0 (DEVICE, "Error in allocation memory for ITF buckets");
            dwRetCode = ERROR_NOT_ENOUGH_MEMORY;
            break;
        }
    
        for (dwIndex=0; dwIndex < INTF_TABLE_BUCKETS; dwIndex++)
        {
            g_ITFTable.pITFBuckets[dwIndex].pItf=NULL;
        }
    
        ACQUIRE_WRITE_LOCK (&g_ITFLock)

        // Enumerate all the interfaces and start EAPOL state machine
        // on interfaces which are of LAN type

        if ((dwRetCode = ElEnumAndOpenInterfaces (NULL, NULL)) != NO_ERROR)
        {
            TRACE1(INIT, "ElMediaInit: ElEnumAndOpenInterfaces failed with dwRetCode = %d", 
                    dwRetCode );
                    
            RELEASE_WRITE_LOCK (&g_ITFLock);
            break;
        }
        else
        {
            RELEASE_WRITE_LOCK (&g_ITFLock);
            TRACE0(INIT, "ElMediaInit: ElEnumAndOpenInterfaces successful");
        }
        
    } while (FALSE);
        
    if (dwRetCode == NO_ERROR)
    {
        TRACE0(INIT, "ElMediaInit successful");
    }
    else
    {
    }

    return dwRetCode;
}

    
//
// ElMediaDeInit
// 
// Description:
//
// Called on EAPOL service shutdown to de-initialize all the media 
// related events and callback functions
// 
//
// Arguments:
//
// Return Values:
//

DWORD
ElMediaDeInit (
        )
{
    DWORD       dwRetCode = NO_ERROR;

    TRACE0 (INIT, "ElMediaDeInit: Entered");

    // DeRegister Media Sense detection of MEDIA_CONNECT and MEDIA_DISCONNECT
    // of interfaces

    if (g_dwModulesStarted & WMI_MODULE_STARTED)
    {
        if ((dwRetCode = ElMediaSenseRegister (FALSE)) != NO_ERROR )
        {
            TRACE1(INIT, "ElMediaDeInit: ElMediaSenseRegister failed with dwRetCode = %d", 
                    dwRetCode );
            // log
        }
        else
        {
            TRACE0(INIT, "ElMediaDeInit: ElMediaSenseRegister successful");
        }
            
        g_dwModulesStarted &= ~WMI_MODULE_STARTED;
    }

    // Deregister device notifications that may have been posted

    if (g_dwModulesStarted & DEVICE_NOTIF_STARTED)
    {
        if ((dwRetCode = ElDeviceNotificationRegister (FALSE)) != NO_ERROR)
        {
            TRACE1(INIT, "ElMediaDeInit: ElDeviceNotificationRegister failed with dwRetCode = %d", 
                    dwRetCode );
            // log
        }
        else
        {
            TRACE0(INIT, "ElMediaDeInit: ElDeviceNotificationRegister successful");
        }

        g_dwModulesStarted &= ~DEVICE_NOTIF_STARTED;
    }

    // Shutdown EAPOL state machine
            
    if (g_dwModulesStarted & EAPOL_MODULE_STARTED)
    {
        if ((dwRetCode = ElEAPOLDeInit()) != NO_ERROR)
        {
            TRACE1(INIT, "ElMediaDeInit: ElEAPOLDeInit failed with dwRetCode = %d", 
                    dwRetCode );
            // log
        }
        else
        {
            TRACE0(INIT, "ElMediaDeInit: ElEAPOLDeInit successful");
        }

        g_dwModulesStarted &= ~EAPOL_MODULE_STARTED;
    }

    // Free the interface table

    if (READ_WRITE_LOCK_CREATED(&(g_ITFLock)))
    {
        ACQUIRE_WRITE_LOCK (&(g_ITFLock));
#if 0
    if (!FREE(g_ITFTable.pITFBuckets))
    {
        TRACE0 (EAPOL, "ElMediaDeInit: Error in freeing ITF table memory");
        dwRetCode = GetLastError();
    }
#endif
        FREE(g_ITFTable.pITFBuckets);
        ZeroMemory (&g_ITFTable, sizeof (g_ITFTable));
        RELEASE_WRITE_LOCK (&(g_ITFLock));
    
        // Delete ITF table lock

        DELETE_READ_WRITE_LOCK(&(g_ITFLock));

        // Delete NLA lock

        DELETE_READ_WRITE_LOCK(&(g_NLALock));
    }
    
    TRACE0(INIT, "ElMediaDeInit completed");

    return dwRetCode;
}


//
// Description:
//
// Function called to register CallBack function with WMI
// for MEDIA_CONNECT/MEDIA_DISCONNECT events
//
// Arguments: 
//      fRegister - True = Register for Media Sense
//                  False = Deregister Media Sense requests
// Return values:
//      NO_ERROR  - Successful
//      non-zero  - Error
//

DWORD
ElMediaSenseRegister (
        IN  BOOL        fRegister
        )
{
    DWORD       dwRetCode = NO_ERROR;
    PVOID       pvDeliveryInfo = ElMediaSenseCallback;

    dwRetCode = WmiNotificationRegistrationA(
                    (LPGUID)(&GUID_NDIS_STATUS_MEDIA_CONNECT),
                    (BOOLEAN)fRegister,    
                    pvDeliveryInfo,
                    (ULONG_PTR)NULL,
                    NOTIFICATION_CALLBACK_DIRECT );

    if (dwRetCode != NO_ERROR) 
    {
		TRACE1(INIT, "ElMediaSenseRegister: Error %d in WmiNotificationRegistration:GUID_NDIS_STATUS_MEDIA_CONNECT", dwRetCode);
        return( dwRetCode );
    }

    dwRetCode = WmiNotificationRegistrationA(
                    (LPGUID)(&GUID_NDIS_STATUS_MEDIA_DISCONNECT),
                    (BOOLEAN)fRegister,
                    pvDeliveryInfo,
                    (ULONG_PTR)NULL,
                    NOTIFICATION_CALLBACK_DIRECT );

    if (dwRetCode != NO_ERROR)
    {
		TRACE1(INIT, "ElMediaSenseRegister: Error %d in WmiNotificationRegistration:GUID_NDIS_STATUS_MEDIA_DISCONNECT", dwRetCode);
        return( dwRetCode );
    }

    TRACE1 (INIT, "ElMediaSenseRegister - completed with RetCode %d", dwRetCode);

    return( dwRetCode );
}


//
// ElDeviceNotificationRegister
// 
// Description:
//
// Function called to register for device addition/removal notifications
//
// Arguments: 
//      fRegister - True = Register for Device Notifications
//                  False = Deregister Device Notifications
//
// Return values:
//      NO_ERROR  - Successful
//      non-zero  - Error
//

DWORD
ElDeviceNotificationRegister (
        IN  BOOL        fRegister
        )
{
    HANDLE      hDeviceNotification = NULL;
    DWORD       dwRetCode = NO_ERROR;

#ifdef EAPOL_SERVICE

    DEV_BROADCAST_DEVICEINTERFACE   PnPFilter;

    ZeroMemory (&PnPFilter, sizeof(PnPFilter));

    PnPFilter.dbcc_size = sizeof(PnPFilter);
    PnPFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
    PnPFilter.dbcc_classguid    = GUID_NDIS_LAN_CLASS;

    // NOTE:
    // EAPOL service is only working with ANSI strings, hence the ANSI calls

    hDeviceNotification = RegisterDeviceNotificationA(
                                (HANDLE)g_hServiceStatus,
                                &PnPFilter,
                                DEVICE_NOTIFY_SERVICE_HANDLE );

    if (hDeviceNotification == NULL)
    {
        dwRetCode = GetLastError();

        TRACE1 (DEVICE, "ElDeviceNotificationRegister failed with error %ld",
                dwRetCode);
    }

#endif

    return dwRetCode;
}


//
// ElDeviceNotificationHandler
// 
// Description:
//
// Function called to handle device notifications for interface addition/
// removal
//
// Arguments:
//      lpEventData - interface information
//      dwEventType - notification type 
//

DWORD
ElDeviceNotificationHandler (
        IN  VOID        *lpEventData,
        IN  DWORD       dwEventType
        )
{
    DWORD                           dwEventStatus = 0;
    DEV_BROADCAST_DEVICEINTERFACE   *pInfo = 
        (DEV_BROADCAST_DEVICEINTERFACE *) lpEventData;
    PVOID                           pvBuffer = NULL;
    DWORD                           dwRetCode = NO_ERROR;

    TRACE0 (DEVICE, "ElDeviceNotificationHandler entered");

    do
    {

        if (g_hEventTerminateEAPOL == NULL)
        {
            break;
        }

        // Check if have already gone through EAPOLCleanUp before

        if ((dwEventStatus = WaitForSingleObject (
                    g_hEventTerminateEAPOL,
                    0)) == WAIT_FAILED)
        {
            dwRetCode = GetLastError ();
            TRACE1(INIT, "ElDeviceNotificationHandler: WaitForSingleObject failed with error %ld, Terminating !!!",
                    dwRetCode);
            // log
    
            break;
        }

        if (dwEventStatus == WAIT_OBJECT_0)
        {
            TRACE0(INIT, "ElDeviceNotificationHandler: g_hEventTerminateEAPOL already signaled, returning");
            break;
        }
    
        if (lpEventData == NULL)
        {
            dwRetCode = ERROR_INVALID_DATA;
            TRACE0 (DEVICE, "ElDeviceNotificationHandler: lpEventData == NULL");
            break;
        }
    
        if (pInfo->dbcc_devicetype == DBT_DEVTYP_DEVICEINTERFACE)
        {
            TRACE0 (DEVICE, "ElDeviceNotificationHandler: Event for Interface type");
    
            if ((pvBuffer = MALLOC (pInfo->dbcc_size + 8)) == NULL)
            {
                dwRetCode = ERROR_NOT_ENOUGH_MEMORY;
                TRACE0 (DEVICE, "ElDeviceNotificationHandler: MALLOC failed for pvBuffer");
                break;
            }
    
            *((DWORD *)pvBuffer) = dwEventType;
            memcpy ((PBYTE)pvBuffer + 8, (PBYTE)pInfo, pInfo->dbcc_size);
    
            if (!QueueUserWorkItem (
                (LPTHREAD_START_ROUTINE)ElDeviceNotificationHandlerWorker,
                pvBuffer,
                WT_EXECUTELONGFUNCTION))
            {
                dwRetCode = GetLastError();
                TRACE1 (DEVICE, "ElDeviceNotificationHandler: QueueUserWorkItem failed with error %ld",
                        dwRetCode);
	            break;
            }

        }
        else
        {
            TRACE0 (DEVICE, "ElDeviceNotificationHandler: Event NOT for Interface type");
        }

    }
        
    while (FALSE);
    
    TRACE1 (DEVICE, "ElDeviceNotificationHandler completed with error %ld",
            dwRetCode);

    if (dwRetCode != NO_ERROR)
    {
        if (pvBuffer != NULL)
        {
            FREE (pvBuffer);
        }
    }

    return dwRetCode;
}


//
// ElDeviceNotificationHandlerWorker
// 
// Description:
//
// Worker function for ElDeviceNotificationHandlerWorker
//
// Arguments:
//      pvContext - interface information
//

VOID
ElDeviceNotificationHandlerWorker (
        IN  PVOID       pvContext
        )
{
    EAPOL_PCB                       *pPCB = NULL;
    EAPOL_ITF                       *pITF = NULL;
    HANDLE                          hDevice = NULL;
    DEV_BROADCAST_DEVICEINTERFACE   *pInfo = NULL;
    DWORD                           dwEventType = 0;
    DWORD                           dwRetCode = NO_ERROR;

    TRACE0 (DEVICE, "ElDeviceNotificationHandlerWorker: Entered");

    do
    {
        if (pvContext == NULL)
        {
            TRACE0 (DEVICE, "ElDeviceNotificationHandlerWorker: pvContext == NULL");
            break;
        }

        dwEventType = *((DWORD *) pvContext);
        pInfo = (DEV_BROADCAST_DEVICEINTERFACE*)((PBYTE)pvContext + 8);

        if ((dwEventType == DBT_DEVICEARRIVAL) ||
                (dwEventType == DBT_DEVICEREMOVECOMPLETE))
        {
            // Extract GUID from the \Device\GUID string

            WCHAR   *pwszGUIDStart = NULL;
            WCHAR   *pwszGUIDEnd = NULL;
            CHAR    *pszGUIDStart = NULL;
            WCHAR   chGUIDSaveLast;
            WCHAR   Buffer1[256];
            CHAR    Buffer2[256];
            UNICODE_STRING      UnicodeGUID;
            ANSI_STRING         AnsiGUID;
        
            TRACE0 (DEVICE, "ElDeviceNotificationHandlerWorker: Interface arr/rem");

            pwszGUIDStart  = wcsrchr( pInfo->dbcc_name, L'{' );
            pwszGUIDEnd    = wcsrchr( pInfo->dbcc_name, L'}' );

            if ((pwszGUIDStart != NULL) && (pwszGUIDEnd != NULL))
            {
                chGUIDSaveLast = *(pwszGUIDEnd+1);
                            
                // Ignore the leading '{' and the trailing '}'
                // in the GUID

                *(pwszGUIDEnd) = L'\0';
                pwszGUIDStart ++;

                UnicodeGUID.Buffer = Buffer1;
                UnicodeGUID.MaximumLength = 256*sizeof(WCHAR);
                RtlInitUnicodeString (&UnicodeGUID, pwszGUIDStart);

                // Will not fail since memory is already allocated for the 
                // conversion

                AnsiGUID.Buffer = Buffer2;
                AnsiGUID.MaximumLength = 256;
                RtlUnicodeStringToAnsiString (&AnsiGUID, 
                        &UnicodeGUID, FALSE);

                AnsiGUID.Buffer[AnsiGUID.Length] = '\0';
                pszGUIDStart = AnsiGUID.Buffer;

                TRACE1 (DEVICE, "ElDeviceNotificationHandlerWorker: For interface %s",
                        pszGUIDStart);

                // Interface was added

                if (dwEventType == DBT_DEVICEARRIVAL)
                {
                    ACQUIRE_WRITE_LOCK (&g_ITFLock);

                    TRACE0(DEVICE, "ElDeviceNotificationHandlerWorker: Callback for device addition");
        
                    if ((dwRetCode = ElEnumAndOpenInterfaces (NULL, 
                                        pszGUIDStart))
                                        != NO_ERROR)
                    {
                        TRACE1 (DEVICE, "ElDeviceNotificationHandlerWorker: ElEnumAndOpenInterfaces returned error %ld", 
                            dwRetCode);
                    }
        
                    RELEASE_WRITE_LOCK (&g_ITFLock);
                }
                else
                {
        
                    TRACE0(DEVICE, "ElDeviceNotificationHandlerWorker: Callback for device removal");

                    ACQUIRE_WRITE_LOCK (&(g_ITFLock));

                    // Check if EAPOL was actually started on this interface
                    // Verify by checking existence of corresponding 
                    // entry in hash table
        
                    ACQUIRE_WRITE_LOCK (&(g_PCBLock));
                    if ((pPCB = ElGetPCBPointerFromPortGUID(pszGUIDStart))
                            != NULL)
                    {
                        RELEASE_WRITE_LOCK (&(g_PCBLock));
                        TRACE0 (DEVICE, "ElDeviceNotificationHandlerWorker: Found PCB entry for interface");
        
                        if ((pITF = ElGetITFPointerFromInterfaceDesc(
                                        pPCB->pszFriendlyName))
                                        == NULL)
                        {
                            TRACE0 (DEVICE, "ElDeviceNotificationHandlerWorker: Did not find ITF entry when PCB exits, HOW BIZARRE !!!");

                        }

                        if ((dwRetCode = ElDeletePort (
                                        pszGUIDStart, 
                                        &hDevice)) != NO_ERROR)
                        {
                            TRACE1 (DEVICE, "ElDeviceNotificationHandlerWorker: Error in deleting port for %s", 
                                pPCB->pszDeviceGUID);
                        }
        
                        //
                        // Remove interface entry from interface table
                        //
                        
                        if (pITF != NULL)
                        {
                            ElRemoveITFFromTable(pITF);
                                    
                            if (pITF->pszInterfaceDesc)
                            {
                                FREE (pITF->pszInterfaceDesc);
                            }
                            if (pITF->pszInterfaceGUID)
                            {
                                FREE (pITF->pszInterfaceGUID);
                            }
                            if (pITF)
                            {
                                FREE (pITF);
                            }
                        }

                        // Close the handle to the NDISUIO driver

                        if (hDevice != NULL)
                        {
                            if ((dwRetCode = ElCloseInterfaceHandle (hDevice)) 
                                    != NO_ERROR)
                            {
                                TRACE1 (DEVICE, 
                                    "ElDeviceNotificationHandlerWorker: Error in ElCloseInterfaceHandle %d", 
                                    dwRetCode);
                            }
                        }
            
                        TRACE1 (DEVICE, "ElDeviceNotificationHandlerWorker: Port deleted %s", 
                                pszGUIDStart);

                    }
                    else
                    {
                        RELEASE_WRITE_LOCK (&(g_PCBLock));

                        // Ignore device removal 
                        //
                        TRACE0 (DEVICE, "ElDeviceNotificationHandlerWorker: ElGetPCBPointerFromPortGUID did not find any matching entry, ignoring DEVICE REMOVAL");
        
                    }

                    RELEASE_WRITE_LOCK (&g_ITFLock);
                }
            }
        }
        else
        {
            TRACE0 (DEVICE, "ElDeviceNotificationHandlerWorker: Event type is is NOT device arr/rem");
        }

    }
    while (FALSE);

    if (pvContext != NULL)
    {
        FREE (pvContext);
    }

    TRACE1 (DEVICE, "ElDeviceNotificationHandlerWorker completed with error %ld",
            dwRetCode);

    return;
}


//
// ElMediaSenseCallback
// 
// Description:
//
// Callback function called by WMI on MEDIA_CONNECT/MEDIA_DISCONNECT 
// events
//
// Arguments:
//      pWnodeHeader - Pointer to information returned by the event
//      uiNotificationContext - unused
//
// Return values:
//      NO_ERROR - Success
//      non-zero - Error
//

VOID
CALLBACK
ElMediaSenseCallback (
        IN PWNODE_HEADER    pWnodeHeader,
        IN UINT_PTR         uiNotificationContext
        )
{
    DWORD       dwEventStatus = 0;
    PVOID       pvBuffer = NULL;
    DWORD       dwRetCode = NO_ERROR;

    TRACE0 (DEVICE, "ElMediaSenseCallback: Entered");

    do
    {
        if (g_hEventTerminateEAPOL == NULL)
        {
            dwRetCode = NO_ERROR;
            break;
        }

        // Check if have already gone through EAPOLCleanUp before

        if (( dwEventStatus = WaitForSingleObject (
                    g_hEventTerminateEAPOL,
                    0)) == WAIT_FAILED)
        {
            dwRetCode = GetLastError ();
            TRACE1 (INIT, "ElMediaSenseCallback: WaitForSingleObject failed with error %ld, Terminating !!!",
                    dwRetCode);
            // log
    
            break;
        }

        if (dwEventStatus == WAIT_OBJECT_0)
        {
            dwRetCode = NO_ERROR;
            TRACE0 (INIT, "ElMediaSenseCallback: g_hEventTerminateEAPOL already signaled, returning");
            break;
        }

        if (pWnodeHeader == NULL)
        {
            dwRetCode = ERROR_INVALID_DATA;
            TRACE0 (DEVICE, "ElMediaSenseCallback: pWnodeHeader == NULL");
            break;
        }

        if ((pvBuffer = MALLOC (pWnodeHeader->BufferSize)) == NULL)
        {
            dwRetCode = ERROR_NOT_ENOUGH_MEMORY;
            TRACE0 (DEVICE, "ElMediaSenseCallback: MALLOC failed for pvBuffer");
            break;
        }

        memcpy ((PVOID)pvBuffer, (PVOID)pWnodeHeader, pWnodeHeader->BufferSize);

        if (!QueueUserWorkItem (
            (LPTHREAD_START_ROUTINE)ElMediaSenseCallbackWorker,
            pvBuffer,
            WT_EXECUTELONGFUNCTION))
        {
            dwRetCode = GetLastError();
            TRACE1 (DEVICE, "ElMediaSenseCallback: QueueUserWorkItem failed with error %ld",
                    dwRetCode);
	        ASSERT (0);
        }
    }
    while (FALSE);

    if (dwRetCode != NO_ERROR)
    {
        TRACE1 (DEVICE, "ElMediaSenseCallback: Failed with error %ld",
                dwRetCode);

        if (pvBuffer != NULL)
        {
            FREE (pvBuffer);
        }
    }

}


//
// ElMediaSenseCallbackWorker
// 
// Description:
//
// Worker function for ElMediaSenseCallback and executes in a separate
// thread
//
// Arguments:
//      pvContext - Pointer to information returned by the media-sense event
//
// Return values:
//      NO_ERROR - Success
//      non-zero - Error
//

VOID
ElMediaSenseCallbackWorker (
        IN  PVOID       pvContext
        )
{
    PWNODE_HEADER           pWnodeHeader = (PWNODE_HEADER)pvContext;
    PWNODE_SINGLE_INSTANCE  pWnode   = (PWNODE_SINGLE_INSTANCE)pWnodeHeader;
    LPWSTR                  lpwsName;
    CHAR                    *psName;
    CHAR                    *psDeviceName = NULL;
    CHAR                    psLength;
    USHORT                  cpsLength;
    CHAR                    *psNameEnd;
    LPWSTR                  lpwsDeviceName = NULL;
    HANDLE                  hDevice = NULL;
    UNICODE_STRING          uInterfaceDescription;
    ANSI_STRING             InterfaceDescription;
    PCHAR                   pszInterfaceDescription;
    UCHAR			        Buffer[256];
    CHAR                    *pszInterfaceDesc = NULL;
    DWORD                   dwIndex;
    EAPOL_ITF               *pITF;
    ULONG                   i = 0;
    EAPOL_PCB               *pPCB = NULL;
    DWORD                   dwRetCode = NO_ERROR;

    do
    {

#ifdef EAPOL_SERVICE

    if ((g_ServiceStatus.dwCurrentState == SERVICE_STOP_PENDING)
         ||
         (g_ServiceStatus.dwCurrentState == SERVICE_STOPPED))
    {
        TRACE0 (DEVICE, "ElMediaSenseCallbackWorker: Callback received while service was stopping");
        break;
    }

#endif // EAPOL_SERVICE

    if (pWnodeHeader == NULL)
    {
        TRACE0 (DEVICE, "ElMediaSenseCallbackWorker: Callback received with NULL NDIS interface details");

        break;
    }

    psName = (PCHAR)RtlOffsetToPointer(
                                    pWnode,
                                    pWnode->OffsetInstanceName );

    // Get the length of the string
    // Null terminate it 

    cpsLength = (SHORT)( *((SHORT *)psName) );

    if (!(psDeviceName = (CHAR *) MALLOC ((cpsLength+1)*sizeof(CHAR))))
    {
        TRACE0 (DEVICE, "ElMediaSenseCallbackWorker: Error in Memory allocation for psDeviceName");
        break;
    }

    memcpy ((CHAR *)psDeviceName, (CHAR *)psName+sizeof(SHORT), cpsLength);
    psDeviceName[cpsLength] = '\0';
    RtlInitAnsiString (&InterfaceDescription, psDeviceName);
    InterfaceDescription.Buffer[cpsLength] = '\0';

    TRACE3(DEVICE, "ElMediaSenseCallbackWorker: For interface ANSI %s, true= %s, lengt of block = %d", 
            InterfaceDescription.Buffer, psDeviceName, cpsLength);

    
    pszInterfaceDescription = InterfaceDescription.Buffer;
                                
    TRACE1 (DEVICE, "ElMediaSenseCallbackWorker: Interface desc = %s", 
            pszInterfaceDescription);

    //
    // Get the information for the media disconnect.
    //

    if (memcmp( &(pWnodeHeader->Guid), 
                 &GUID_NDIS_STATUS_MEDIA_DISCONNECT, 
                 sizeof(GUID)) == 0)
    {
        // MEDIA DISCONNECT callback 

        TRACE0(DEVICE, "ElMediaSenseCallbackWorker: Callback for sense disconnect");

        ACQUIRE_WRITE_LOCK (&(g_ITFLock))

        // Check if EAPOL was actually started on this interface
        // Verify by checking existence of corresponding entry in hash table

        if ((pITF = ElGetITFPointerFromInterfaceDesc(pszInterfaceDescription))
                != NULL)
        {
            TRACE0 (DEVICE, "ElMediaSenseCallbackWorker: Found ITF entry for interface");

#if 0
            if ((dwRetCode = ElDeletePort (pITF->pszInterfaceGUID, &hDevice)) != NO_ERROR)
            {
                TRACE1 (DEVICE, "ElMediaSenseCallbackWorker: Error in deleting port for %s", 
                        pITF->pszInterfaceGUID);
            }

            // Remove interface entry from interface table

            ElRemoveITFFromTable(pITF);

            if (pITF->pszInterfaceDesc)
            {
                FREE (pITF->pszInterfaceDesc);
                pITF->pszInterfaceDesc = NULL;
            }
            if (pITF->pszInterfaceGUID)
            {
                FREE (pITF->pszInterfaceGUID);
                pITF->pszInterfaceGUID = NULL;
            }
            if (pITF)
            {
                FREE (pITF);
                pITF = NULL;
            }

            // Close the handle to the NDISUIO driver

            if ((dwRetCode = ElCloseInterfaceHandle (hDevice)) != NO_ERROR)
            {
                TRACE1 (DEVICE, 
                        "ElMediaSenseCallbackWorker: Error in ElCloseInterfaceHandle %d", 
                        dwRetCode);
            }
            
            TRACE1 (DEVICE, "ElMediaSenseCallbackWorker: Port deleted %s", 
                    pszInterfaceDescription);
#endif

            ACQUIRE_WRITE_LOCK (&(g_PCBLock));
            pPCB = ElGetPCBPointerFromPortGUID (pITF->pszInterfaceGUID);
            if (pPCB != NULL)
            {
                ACQUIRE_WRITE_LOCK (&(pPCB->rwLock));
                if ((dwRetCode = FSMDisconnected (pPCB)) != NO_ERROR)
                {
                    TRACE1 (DEVICE, "ElMediaSenseCallbackWorker: FSMDisconnected failed with error %ld", 
                        dwRetCode);
                }
                RELEASE_WRITE_LOCK (&(pPCB->rwLock));
            }
            RELEASE_WRITE_LOCK (&(g_PCBLock));

            TRACE1 (DEVICE, "ElMediaSenseCallbackWorker: Port marked disconnected %s", 
                    pszInterfaceDescription);

        }
        else
        {
            // Ignore MEDIA DISCONNECT

            TRACE0 (DEVICE, "ElMediaSenseCallbackWorker: ElHashInterfaceDescToBucket did not find any matching entry, ignoring MEDIA_DISCONNECT");
        }

        RELEASE_WRITE_LOCK (&g_ITFLock);

    }
    else
    {

        if (memcmp( &(pWnodeHeader->Guid), 
                     &GUID_NDIS_STATUS_MEDIA_CONNECT, 
                     sizeof(GUID)) == 0)
        {
            // MEDIA CONNECT callback

            ACQUIRE_WRITE_LOCK (&g_ITFLock)

            TRACE0(DEVICE, "ElMediaSenseCallbackWorker: Callback for sense connect");

            if ((dwRetCode = ElEnumAndOpenInterfaces (
                            pszInterfaceDescription, NULL))
                != NO_ERROR)
            {
                TRACE1 (DEVICE, "ElMediaSenseCallbackWorker: ElEnumAndOpenInterfaces returned error %ld", 
                        dwRetCode);
            }
        
            RELEASE_WRITE_LOCK (&g_ITFLock);

        }
    }

    }
    while (FALSE);

    TRACE1 (DEVICE, "ElMediaSenseCallbackWorker: processed, RetCode = %ld", dwRetCode);


    if (pWnodeHeader != NULL)
    {
        FREE (pWnodeHeader);
    }

    if (psDeviceName != NULL)
    {
        FREE (psDeviceName);
    }

    return;
}


// 
// ElEnumAndOpenInterfaces
// 
// Description:
//
// Enumerates interfaces and intializes EAPOL on desired ones.
//
// If EAPOL is to be started on an interface, it opens a handle to 
// the NDISUIO driver, calls EAPOL to create and initialize PCB for the 
// interface, and finally adds an entry to the interface hashtable.
//
// If pszDesiredGUID is not NULL, all interfaces are enumerated, but 
// EAPOL will be initialized only on the interface whose GUID matches.
//
// If pszDescription is not NULL, all interfaces are enumerated, but 
// EAPOL will be initialized only on the interface whose description matches.
//
// If pszDesiredGUID and pszDescription are both NULL, all interfaces are 
// enumerated. EAPOL will be initialized only on all interfaces that 
// does have an entry in the interface hashtable.
//
//
// Arguments:
//      pszDesiredDescription - Interface Description on which EAPOL is to 
//                  be started
//      pszDesiredGUID - Interface GUID on which EAPOL is to be started
//
// Return values:
//      NO_ERROR - Success
//      non-zero - Error
//

DWORD
ElEnumAndOpenInterfaces (
        CHAR    *pszDesiredDescription,
        CHAR    *pszDesiredGUID
        )
{
    CHAR				EnumerateBuffer[256];
    PNDIS_ENUM_INTF		Interfaces = NULL;
    BYTE                *pbNdisuioEnumBuffer = NULL;
    DWORD               dwNdisuioEnumBufferSize = 0;
    DWORD				dwNicCardStatus = 0;
    DWORD				dwMediaType = 0;
    HANDLE              hDevice = NULL;
    EAPOL_ITF           *pNewITF = NULL;
    DWORD               dwIndex = 0;
    UCHAR               csCurrMacAddress[6];
    UCHAR               csPermMacAddress[6];
    UCHAR               csOidVendData[16];
    BOOL                fSearchByDescription = FALSE;
    BOOL                fSearchByGUID = FALSE;
    DWORD               dwEapTypeToBeUsed = DEFAULT_EAP_TYPE;
    DWORD               dwEapolEnabled = DEFAULT_EAPOL_STATE;
    CHAR                csDummyBuffer[256];
    UNICODE_STRING      TempUString;
    CHAR                *pszGUIDStart = NULL;
    EAPOL_PCB           *pPCB = NULL;
    BOOL                fPCBExists = FALSE;
    DWORD               dwAvailableInterfaces = 0;
    DWORD				dwRetCode = NO_ERROR;


    TRACE2 (DEVICE, "ElEnumAndOpenInterfaces: DeviceDesc = %s, GUID = %s",
            pszDesiredDescription, pszDesiredGUID);

    if (pszDesiredGUID == NULL)
    {
        if (pszDesiredDescription != NULL)
        {
            fSearchByDescription = TRUE;
        }
    }
    else
    {
        if (pszDesiredDescription != NULL)
        {
            return ERROR;
        }
        fSearchByGUID = TRUE;
    }

    ZeroMemory (EnumerateBuffer, 256);
    Interfaces = (PNDIS_ENUM_INTF)EnumerateBuffer;

    //
    // Allocate amount of memory as instructed by NdisEnumerateInterfaces
    // once the API allows querying of bytes required
    //

    Interfaces->TotalInterfaces = 0;
    Interfaces->AvailableInterfaces = 0;
    Interfaces->BytesNeeded = 0;
    if (!NdisEnumerateInterfaces(Interfaces, 256)) 
    {
        dwRetCode = GetLastError ();
        TRACE1 (DEVICE, "ElEnumAndOpenInterfaces: NdisEnumerateInterfaces failed with error %ld",
                dwRetCode);
        return dwRetCode;
    }

    TRACE3 (DEVICE, "ElEnumAndOpenInterfaces: TotalInterfaces = %ld, AvailInt = %ld, Bytes needed = %ld",
            Interfaces->TotalInterfaces, 
            Interfaces->AvailableInterfaces, 
            Interfaces->BytesNeeded);

    dwAvailableInterfaces = Interfaces->AvailableInterfaces;

    dwNdisuioEnumBufferSize = (Interfaces->BytesNeeded + 7) & ~7;

    if (dwNdisuioEnumBufferSize == 0) {
        TRACE0 (DEVICE, "ElEnumAndOpenInterfaces: MALLOC skipped for zero length pbNdisuioEnumBuffer");
        dwRetCode = NO_ERROR;
        return dwRetCode;
    }

    pbNdisuioEnumBuffer = (BYTE *) MALLOC (4*dwNdisuioEnumBufferSize);

    if (pbNdisuioEnumBuffer == NULL)
    {
        TRACE0 (DEVICE, "ElEnumAndOpenInterfaces: MALLOC failed for pbNdisuioEnumBuffer");
        dwRetCode = ERROR_NOT_ENOUGH_MEMORY;
        return dwRetCode;
    }

    Interfaces = (PNDIS_ENUM_INTF)pbNdisuioEnumBuffer;


    // Enumerate all the interfaces present on the machine

    if ((dwRetCode = ElNdisuioEnumerateInterfaces (
                            Interfaces, 
                            dwAvailableInterfaces,
                            4*dwNdisuioEnumBufferSize)) == NO_ERROR)
    {
        ANSI_STRING		InterfaceName;
        ANSI_STRING		InterfaceDescription;
        UNICODE_STRING	UInterfaceName;
        UCHAR			Buffer1[256];
        UCHAR			Buffer2[256];
        WCHAR			Buffer3[256];
        DWORD			i;

        InterfaceName.MaximumLength = 256;
        InterfaceName.Buffer = Buffer1;
        InterfaceDescription.MaximumLength = 256;
        InterfaceDescription.Buffer = Buffer2;

        TRACE1 (DEVICE, "TotalInterfaces=%ld", Interfaces->TotalInterfaces);

        // Update the interface list in the registry that NDISUIO has bound to.
        // The current interface list is just overwritten into the registry.


        if ((dwRetCode = ElUpdateRegistryInterfaceList (Interfaces)) 
                != NO_ERROR)
        {
            TRACE1 (DEVICE, "ElEnumAndOpenInterfaces: ElUpdateInterfaceList failed with error =%ld", 
                    dwRetCode);

            // log
        }

        for (i=0; i < Interfaces->TotalInterfaces; i++)
        {
            if (Interfaces->Interface[i].DeviceName.Buffer != NULL)
            {
                InterfaceName.Length = 0;
                InterfaceName.MaximumLength = 256;
                if (RtlUnicodeStringToAnsiString(&InterfaceName, 
                        &Interfaces->Interface[i].DeviceName, FALSE) != STATUS_SUCCESS)
                {
                    TRACE0 (INIT, "Error in RtlUnicodeStringToAnsiString for DeviceName");
                }

                InterfaceName.Buffer[InterfaceName.Length] = '\0';
            }
            else
            {
                TRACE0(INIT, "NdisEnumerateInterfaces: Device Name was NULL");
                continue;
            }

            TRACE1(INIT, "Device: %s", InterfaceName.Buffer);

            // BUBUG:
            // Should Device Name be retained in Unicode or converted to ANSI

            InterfaceDescription.Length = 0;
            InterfaceDescription.MaximumLength = 256;
            if (RtlUnicodeStringToAnsiString(&InterfaceDescription, 
                    &Interfaces->Interface[i].DeviceDescription, FALSE) != STATUS_SUCCESS)
            {
                    TRACE0 (INIT, "Error in RtlUnicodeStringToAnsiString for DeviceDescription");
            }

            InterfaceDescription.Buffer[InterfaceDescription.Length] = '\0';

            TRACE1(INIT, "Description: %s", InterfaceDescription.Buffer);


            // Create PCB for interface and start EAPOL state machine

            // EAPOL requested be started only a particular
            // interface

            if (fSearchByDescription)
            {
                if (strcmp (InterfaceDescription.Buffer,
                            pszDesiredDescription)
                        != 0)
                {
                    // No match, continue with next interface
                    continue;
                }

                TRACE1 (DEVICE, "ElEnumAndOpenInterfaces: Found interface after enumeration %s", InterfaceDescription.Buffer);
            }

            if (fSearchByGUID)
            {
                if (strstr (InterfaceName.Buffer,
                            pszDesiredGUID)
                        == NULL)
                {
                    // No match, continue with next interface
                    continue;
                }

                TRACE1 (DEVICE, "ElEnumAndOpenInterfaces: Found interface after enumeration %s", InterfaceName.Buffer);
            }

            {
                // Extract GUID-string out of device name

                CHAR    *pszGUIDEnd;
                CHAR    *pszGUID;
                CHAR    chGUIDSaveLast;

                pszGUID = InterfaceName.Buffer;
                pszGUIDStart  = strchr( pszGUID, '{' );
                pszGUIDEnd    = strchr( pszGUID, '}' );

                if (RtlAnsiStringToUnicodeString (
                                                &TempUString,
                                                &InterfaceName,
                                                TRUE))
                {
                    TRACE0 (DEVICE, "Error in RtlAnsiStringToUnicodeString for TempUString");
                }
                    
                // Query MAC address for the interface

                if (!NdisQueryHwAddress (
                                &TempUString,
                                csCurrMacAddress,
                                csPermMacAddress,
                                csOidVendData))
                {
                    RtlFreeUnicodeString (&TempUString);
                    dwRetCode = GetLastError();
                    TRACE1 (DEVICE, "ElEnumAndOpenInterfaces: Error in NdisQueryHwAddress = %d", dwRetCode);
                    dwRetCode = NO_ERROR;
                    continue;
                }

                RtlFreeUnicodeString (&TempUString);
                    
                TRACE0 (DEVICE, "ElEnumAndOpenInterfaces: NdisQueryHwAddress successful");

                if (pszGUIDStart != NULL)
                {
                    chGUIDSaveLast = *(pszGUIDEnd);
                    
                    // Ignore the leading '{' and the trailing '}'
                    // in the GUID

                    *(pszGUIDEnd) = (CHAR)NULL;
                    pszGUIDStart ++;

                }

                // Verify if a PCB already exists for the interface
                // This is possible if no media disconnect was received
                // after the initial media connect

                ACQUIRE_WRITE_LOCK (&g_PCBLock);

                pPCB = ElGetPCBPointerFromPortGUID (pszGUIDStart);

                RELEASE_WRITE_LOCK (&g_PCBLock);

                // Restore interface buffer

                *(pszGUIDEnd) = chGUIDSaveLast;

                if (pPCB != NULL)
                {
                    // Point to existing handle

                    hDevice = pPCB->hPort;
                    fPCBExists = TRUE;
                    dwRetCode = NO_ERROR;
                    TRACE0 (INIT, "ElEnumAndOpenInterfaces: Found PCB already existing for interface");
                }
                else
                {
                    TRACE0 (INIT, "ElEnumAndOpenInterfaces: Did NOT find PCB already existing for interface");

                    // Open handle to ndisuio driver

                    if ((dwRetCode = ElOpenInterfaceHandle (
                                    InterfaceName.Buffer,
                                    &hDevice
                                    )) != NO_ERROR)
                    {
                        TRACE1 (INIT, "ElEnumAndOpenInterfaces: ElOpenInterfaceHandle failed with error = %d\n",
                            dwRetCode );
                    }
                }

                *(pszGUIDEnd) = (CHAR)NULL;

            }

            if (dwRetCode != NO_ERROR)
            {
                TRACE0 (INIT, "ElEnumAndOpenInterfaces: Failed to open handle");
                dwRetCode = NO_ERROR;
                continue;
            }
            else
            {
                // Verify if EAPOL is to be started on the interface
                // at all

                if ((dwRetCode = ElGetInterfaceParams (
                                pszGUIDStart,
                                &dwEapTypeToBeUsed,
                                csDummyBuffer,
                                &dwEapolEnabled
                                )) != NO_ERROR)
                {
                    TRACE2 (INIT, "ElEnumAndOpenInterfaces: ElGetInterfaceParams failed with error %ld for interface %s",
                            dwRetCode, pszDesiredGUID);

                    dwEapTypeToBeUsed = DEFAULT_EAP_TYPE;
                    dwEapolEnabled = DEFAULT_EAPOL_STATE;

                    if (dwEapolEnabled)
                    {
                        dwRetCode = NO_ERROR;
                    }
                    else
                    {

                        // Close the handle to the ndisuio driver

                        if ((dwRetCode = ElCloseInterfaceHandle (hDevice)) 
                                != NO_ERROR)
                        {
                            TRACE1 (DEVICE, 
                                "ElEnumAndOpenInterfaces: Error in ElCloseInterfaceHandle %d", 
                                dwRetCode);
                        }
                        dwRetCode = NO_ERROR;

                        TRACE1 (INIT, "EAPOL will not be started on %s", InterfaceName.Buffer);
                        // Continue with other interfaces
                        continue;
                    }

                }

                if (dwEapolEnabled != EAPOL_ENABLED)
                {
                    TRACE1 (INIT, "EAPOL not to be started on interface %s",
                            pszDesiredGUID);

                    // Close the handle to the ndisuio driver

                    if ((dwRetCode = ElCloseInterfaceHandle (hDevice)) 
                            != NO_ERROR)
                    {
                        TRACE1 (DEVICE, 
                            "ElEnumAndOpenInterfaces: Error in ElCloseInterfaceHandle %d", 
                            dwRetCode);
                    }

                    // Continue with the next interface

                    dwRetCode = NO_ERROR;
                    continue;
                }
                
                // ISSUE:
                // What MAC address should we pass to CreatePort
                // perm or cur

                // Create EAPOL PCB and start state machine

                if ((dwRetCode = ElCreatePort (
                                hDevice,
                                pszGUIDStart,
                                InterfaceDescription.Buffer,
                                csCurrMacAddress
                                )) != NO_ERROR)
                {
                    TRACE1 (DEVICE, "ElEnumAndOpenInterfaces: Error in CreatePort = %d", dwRetCode);

                    // Close the handle to the ndisuio driver

                    if ((dwRetCode = ElCloseInterfaceHandle (hDevice)) != NO_ERROR)
                    {
                        TRACE1 (DEVICE, 
                            "ElEnumAndOpenInterfaces: Error in ElCloseInterfaceHandle %d", 
                            dwRetCode);
                    }

                    // Continue with the next interface

                    dwRetCode = NO_ERROR;
                    continue;
                }
                else
                {
                    TRACE0 (DEVICE, "ElEnumAndOpenInterfaces: CreatePort successful");

                    // If PCB already existed, do not add to the hash
                    // table

                    if (fPCBExists == TRUE)
                    {
                        TRACE0 (DEVICE, "ElEnumAndOpenInterfaces: PCB already existed, skipping Interface hash table addition");
                        fPCBExists = FALSE;
                        dwRetCode = NO_ERROR;
                        continue;
                    }

                    
                    // Add entry to Interface hash table

                    dwIndex = ElHashInterfaceDescToBucket (
                        InterfaceDescription.Buffer);
                    pNewITF = (PEAPOL_ITF) 
                        MALLOC (sizeof (EAPOL_ITF));
                    if (pNewITF != NULL) 
                    {
                        pNewITF->pszInterfaceGUID = 
                            (CHAR *) MALLOC (strlen(pszGUIDStart) + 1);
                        if (pNewITF->pszInterfaceGUID != NULL)
                        {
                            memcpy (pNewITF->pszInterfaceGUID,
                                pszGUIDStart,
                                strlen (pszGUIDStart));
                            pNewITF->pszInterfaceGUID[strlen (pszGUIDStart)] = '\0';
                        }
                        pNewITF->pszInterfaceDesc = 
                            (CHAR *) MALLOC (strlen(InterfaceDescription.Buffer) + 1);
                        if (pNewITF->pszInterfaceDesc != NULL)
                        {
                            memcpy (pNewITF->pszInterfaceDesc,
                                InterfaceDescription.Buffer,
                                strlen (InterfaceDescription.Buffer));
                            pNewITF->pszInterfaceDesc[strlen (InterfaceDescription.Buffer)] = '\0';
                        }


                        pNewITF->pNext = 
                            g_ITFTable.pITFBuckets[dwIndex].pItf;
                        g_ITFTable.pITFBuckets[dwIndex].pItf = pNewITF;

                        if ( (pNewITF->pszInterfaceGUID == NULL) ||
                                (pNewITF->pszInterfaceDesc == NULL))
                        {
                            ElRemoveITFFromTable (pNewITF); 
                            if (pNewITF->pszInterfaceDesc)
                            {
                                FREE (pNewITF->pszInterfaceDesc);
                            }
                            if (pNewITF->pszInterfaceGUID)
                            {
                                FREE (pNewITF->pszInterfaceGUID);
                            }
                            if (pNewITF)
                            {
                                FREE (pNewITF);
                            }

                            pNewITF = NULL;

                            TRACE0 (DEVICE, "ElEnumAndOpenInterfaces: Error in memory allocation, ITF structure not created");

                        }
                    }

                    // Could not create new interface entry
                    // Delete Port entry created for this GUID

                    if (pNewITF == NULL)
                    {
                        if ((dwRetCode = ElDeletePort (
                                        pszGUIDStart,
                                        &hDevice)) != NO_ERROR)
                        {
        
                            TRACE1 (DEVICE, "ElEnumAndOpenInterfaces: Error in deleting port for %s", 
                                    pszGUIDStart);
                            // log
                        }

                        // Close the handle to the NDISUIO driver

                        if ((dwRetCode = ElCloseInterfaceHandle (
                                        hDevice)) != NO_ERROR)
                        {
                            TRACE1 (DEVICE, 
                                    "ElMediaSenseCallback: Error in ElCloseInterfaceHandle %d", 
                                    dwRetCode);
                            // log
                        }
                    }
                    else
                    {
                        TRACE3 (DEVICE, "ElEnumAndOpenInterfaces: Added to hash table GUID= %s : Desc= %s at Index=%d",
                                pNewITF->pszInterfaceGUID,
                                pNewITF->pszInterfaceDesc,
                                dwIndex
                                );
                        //
                        // Interface Handle created successfully
                        // Notify Zero Config about it
                        //
                    }
                }
            }
        } // for (i=0; i < Interfaces
    }
    else
    {
        TRACE1(INIT, "ElEnumAndOpenInterfaces: ElNdisuioEnumerateInterfaces failed with error %d", 
                dwRetCode);
    }

    TRACE1(INIT, "ElEnumAndOpenInterfaces: Completed with retcode = %d", 
            dwRetCode);

    if (pbNdisuioEnumBuffer != NULL)
    {
        FREE(pbNdisuioEnumBuffer);
    }

    return dwRetCode;
}


//
// ElGetCardStatus
//
// Function to query the media information for an interface
//
// Input arguments:
//  pDeviceName - Unicode device name
//
// Return values:
//  pdwNetCardStatus - Media state
//  pdwMediaType    - Media type as exposed to NDIS e.g. 802.3
//

DWORD
ElGetCardStatus (  
        PUNICODE_STRING pDeviceName, 
        DWORD           *pdwNetCardStatus,
        DWORD           *pdwMediaType
        )
{
    NIC_STATISTICS 	Stats;
    DWORD		dwNicStatus = 0;
    DWORD		dwRetCode = NO_ERROR;

    Stats.Size = sizeof(NIC_STATISTICS);
    if (NdisQueryStatistics(pDeviceName, &Stats))
    {
        switch (Stats.MediaState)
        {
            case MEDIA_STATE_CONNECTED:
                dwNicStatus = MEDIA_STATE_CONNECTED;
                TRACE0(INIT, "ElGetCardStatus: dwNicStatus = NETCARD_CONNECTED\n");
                break;
            case MEDIA_STATE_DISCONNECTED:
                dwNicStatus = MEDIA_STATE_DISCONNECTED;
                TRACE0(INIT, "ElGetCardStatus: dwNicStatus = NETCARD_DISCONNECTED\n");
                break;
            default:
                dwNicStatus = MEDIA_STATE_UNKNOWN;
                TRACE0(INIT, "ElGetCardStatus: dwNicStatus = NETCARD_STATUS_UNKNOWN\n");
                break;
        }

        switch (Stats.MediaType)
        {
            case NdisMedium802_3:
                TRACE0 (INIT, "ElGetCardStatus: MediaType is 802.3");
                break;
            default:
                TRACE0 (INIT, "ElGetCardStatus: MediaType is NOT *802.3*");
                break;
        }

        *pdwMediaType = Stats.MediaType;
    }
    else
    {
        dwRetCode = GetLastError ();
        TRACE1(INIT, "ElGetCardStatus: NdisQueryStatistics failed with error %d\n", dwRetCode);
    }

    *pdwNetCardStatus = dwNicStatus;

    return dwRetCode;
}


//
// ElOpenInterfaceHandle
// 
// Description:
//
// Function called to open handle to the NDISUIO driver for an interface.
//
// Arguments:
//      DeviceName - Identifier for the interface is of the 
//                     form \Device\{GUID String}
//      phDevice - Output pointer to handle of NDISUIO driver for 
//                      the interface
//
// Return values:
//      NO_ERROR - success
//      non-zero - error
//

DWORD
ElOpenInterfaceHandle (
        IN  CHAR        *pszDeviceName,
        OUT HANDLE      *phDevice
        )
{
    DWORD   dwDesiredAccess;
    DWORD   dwShareMode;
    LPSECURITY_ATTRIBUTES   lpSecurityAttributes = NULL;
    DWORD   dwCreationDistribution;
    DWORD   dwFlagsAndAttributes;
    HANDLE  hTemplateFile;
    HANDLE  hHandle;
    DWORD   dwRetCode = NO_ERROR;
    WCHAR   wNdisDeviceName[MAX_NDIS_DEVICE_NAME_LEN];
    INT     wNameLength;
    INT     NameLength = strlen(pszDeviceName);
    DWORD   dwBytesReturned;
    USHORT  wEthernetType=0x8081;
    INT     i;

    dwDesiredAccess = GENERIC_READ | GENERIC_WRITE;
    dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
    dwCreationDistribution = OPEN_EXISTING;
    dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED;
    hTemplateFile = (HANDLE)INVALID_HANDLE_VALUE;

    TRACE1 (INIT, "ElOpenInterfaceHandle: Opening handle for %s", pszDeviceName);

    do 
    {

        hHandle = CreateFileA(
                    pNdisuioDevice,
                    dwDesiredAccess,
                    dwShareMode,
                    lpSecurityAttributes,
                    dwCreationDistribution,
                    dwFlagsAndAttributes,
                    hTemplateFile
                    );

        if (hHandle == INVALID_HANDLE_VALUE)
        {
            *phDevice = NULL;
            dwRetCode = GetLastError();
            TRACE1 (INIT, "ElOpenInterfaceHandle: Failed in CreateFile with error %d", dwRetCode);
            break;
        }
        else
        {
            *phDevice = hHandle;
        }
    
    
        // Convert to unicode string - non-localized...
        
        wNameLength = 0;
        for (i = 0; i < NameLength && i < MAX_NDIS_DEVICE_NAME_LEN-1; i++)
        {
            wNdisDeviceName[i] = (WCHAR)pszDeviceName[i];
            wNameLength++;
        }
        wNdisDeviceName[i] = L'\0';
    
        TRACE1(DEVICE, "ElOpenInterfaceHandle: Trying to access NDIS Device: %ws\n", 
                wNdisDeviceName);
    

        if (!(DeviceIoControl(
                *phDevice,
                IOCTL_NDISUIO_OPEN_DEVICE,
                (LPVOID)&wNdisDeviceName[0],
                wNameLength*sizeof(WCHAR),
                NULL,
                0,
                &dwBytesReturned,
                NULL)))
                
        {
            *phDevice = NULL;
            if ((dwRetCode = GetLastError()) == 0)
            {
                dwRetCode = ERROR_IO_DEVICE;
            }
            TRACE1(DEVICE, "ElOpenInterfaceHandle: Error in accessing NDIS Device: %ws", wNdisDeviceName);
            break;
        }
    
        // IOCTL down the Ethernet type

        if (!(DeviceIoControl(
                *phDevice,
                IOCTL_NDISUIO_SET_ETHER_TYPE,
                (LPVOID)&wEthernetType,
                sizeof(USHORT),
                NULL,
                0,
                &dwBytesReturned,
                NULL)))
                
        {
            *phDevice = NULL;
            if ((dwRetCode = GetLastError()) == 0)
            {
                dwRetCode = ERROR_IO_DEVICE;
            }
            TRACE1(DEVICE, "ElOpenInterfaceHandle: Error in ioctling ETHER type : %ws", wNdisDeviceName);
            break;
        }

        // Bind for asynchronous I/O handling of Read/Write data
        // Depending on whether it is completion for Readfile() or WriteFile()
        // ElIoCompletionRoutine will call ElReadCompletionRoutine
        // or ElWriteCompletionRoutine
       
        if (!BindIoCompletionCallback(
                *phDevice,
                ElIoCompletionRoutine,
                0
                ))
        {
            *phDevice = NULL;
            dwRetCode = GetLastError();
            TRACE1 (DEVICE, "ElOpenInterfaceHandle: Error in BindIoCompletionCallBac %d", dwRetCode);
            break;
        }
        
    } while (FALSE);

    // Cleanup if there is error

    if (dwRetCode != NO_ERROR)
    {
        if (hHandle != INVALID_HANDLE_VALUE)
        {
            if (!CloseHandle(hHandle))
            {
                dwRetCode = GetLastError();
                TRACE1 (INIT, "ElOpenInterfaceHandle: Error in CloseHandle %d", dwRetCode);
            }
        }
    }
        
    TRACE2 (INIT, "ElOpenInterfaceHandle: Opened handle %p with dwRetCode %d", *phDevice, dwRetCode);

    return (dwRetCode);

}


//
// ElCloseInterfaceHandle
// 
// Description:
//
// Function called to close handle to NDISUIO driver for an interface 
//
// Arguments:
//      hDevice - Handle to NDISUIO device for the interface
//
// Return values:
//      NO_ERROR - success
//      non-zero - error
//

DWORD
ElCloseInterfaceHandle (
        IN  HANDLE      hDevice
        )
{
    DWORD   dwRetCode = NO_ERROR;

    TRACE0 (DEVICE, "ElCloseInterfaceHandle entered");

    if (!CloseHandle(hDevice))
    {
        dwRetCode = GetLastError();
        TRACE1 (INIT, "ElCloseInterfaceHandle: Error in CloseHandle %d", 
                dwRetCode);
    }

    return dwRetCode;
}


//
// ElReadFromInterface
// 
// Description:
//
// Function called to perform Overlapped read on handle to NDISUIO driver
//
// Arguments:
//      hDevice - Handle to NDISUIO driver for this interface
//      pElBuffer - Context buffer
//      dwBufferLength - Bytes to be read
//
// Return values:
//      NO_ERROR - success
//      non-zero - error
//

DWORD
ElReadFromInterface (
        IN HANDLE           hDevice,
        IN PEAPOL_BUFFER    pElBuffer,
        IN DWORD            dwBufferLength
        )
{
    DWORD   dwRetCode = NO_ERROR;

    if (!ReadFile (
                hDevice,
                pElBuffer->pBuffer,
                dwBufferLength,
                NULL,
                &pElBuffer->Overlapped
                ))
    {
        dwRetCode = GetLastError();
            
        if (dwRetCode == ERROR_IO_PENDING)
        {
            // Pending status is fine, we are doing OVERLAPPED read

            dwRetCode = NO_ERROR;
        }
        else
        {
            TRACE1 (DEVICE, "ElReadFromInterface: ReadFile failed with error %d",
                    dwRetCode);
        }
    }

    return dwRetCode;
}


//
// ElWriteToInterface
// 
// Description:
//
// Function called to perform Overlapped write on handle to NDISUIO driver
//
// Arguments:
//      hDevice - Handle to NDISUIO device for this interface
//      pElBuffer - Context buffer
//      dwBufferLength - Bytes to be written
//
// Return values:
//      NO_ERROR - success
//      non-zero - error
//

DWORD
ElWriteToInterface (
        IN HANDLE           hDevice,
        IN PEAPOL_BUFFER    pElBuffer,
        IN DWORD            dwBufferLength
        )
{
    DWORD   dwRetCode = NO_ERROR;
    
    TRACE0 (DEVICE, "ElWriteToInterface entered");

    if (!WriteFile (
                hDevice,
                pElBuffer->pBuffer,
                dwBufferLength,
                NULL,
                &pElBuffer->Overlapped
                ))
    {
        dwRetCode = GetLastError();
            
        if (dwRetCode == ERROR_IO_PENDING)
        {
            // Pending status is fine, we are doing OVERLAPPED write

            dwRetCode = NO_ERROR;
        }
        else
        {
            TRACE1 (DEVICE, "ElWriteToInterface: WriteFile failed with error %d",
                dwRetCode);
        }
    }

    TRACE1 (DEVICE, "ElWriteToInterface completed, dwRetCode = %d", dwRetCode);
    return dwRetCode;
}


// 
// ElHashInterfaceDescToBucket
// 
// Description:
//
// Function called to convert Friendly name of interface into interface hash 
// table index.
//
// Arguments:
//      pszInterfaceDesc - Friendly name of the interface
//
// Return values:
//      Hash table index between from 0 to INTF_TABLE_BUCKETS-1
//

DWORD
ElHashInterfaceDescToBucket (
        IN CHAR *pszInterfaceDesc
        )
{
    return ((DWORD)((atol(pszInterfaceDesc)) % INTF_TABLE_BUCKETS)); 
}


//
// ElGetITFPointerFromInterfaceDesc
//
// Description:
//
// Function called to convert Friendly name of interface to ITF entry pointer
//
// Arguments:
//      pszInterfaceDesc - Friendly name of the interface
//
// Return values:
//      Pointer to interface entry in hash table
//

PEAPOL_ITF
ElGetITFPointerFromInterfaceDesc (
        IN CHAR *pszInterfaceDesc 
        )
{
    EAPOL_ITF   *pITFWalker = NULL;
    DWORD       dwIndex;
    INT         i=0;

    TRACE1 (DEVICE, "ElGetITFPointerFromInterfaceDesc: Desc = %s", pszInterfaceDesc);
        
    if (pszInterfaceDesc == NULL)
    {
        return (NULL);
    }

    dwIndex = ElHashInterfaceDescToBucket (pszInterfaceDesc);

    TRACE1 (DEVICE, "ElGetITFPointerFromItfDesc: Index %d", dwIndex);

    for (pITFWalker = g_ITFTable.pITFBuckets[dwIndex].pItf;
            pITFWalker != NULL;
            pITFWalker = pITFWalker->pNext
            )
    {
        if (strncmp (pITFWalker->pszInterfaceDesc, pszInterfaceDesc, strlen(pszInterfaceDesc)) == 0)
        {
            return pITFWalker;
        }
    }

    return (NULL);
}


//
// ElRemoveITFFromTable
// 
// Description:
//
// Function called to remove an interface entry from the interface hash 
// table
//
// Arguments:
//      pITF - Point to the Interface entry in the hash table
//
// Return values:
// 

VOID
ElRemoveITFFromTable (
        IN EAPOL_ITF *pITF
        )
{
    DWORD       dwIndex;
    EAPOL_ITF   *pITFWalker = NULL;
    EAPOL_ITF   *pITFTemp = NULL;

    if (pITF == NULL)
    {
        TRACE0 (EAPOL, "ElRemoveITFFromTable: Deleting NULL ITF, returning");
        return;
    }

    dwIndex = ElHashInterfaceDescToBucket (pITF->pszInterfaceDesc);
    pITFWalker = g_ITFTable.pITFBuckets[dwIndex].pItf;
    pITFTemp = pITFWalker;

    while (pITFTemp != NULL)
    {
        if (strncmp (pITFTemp->pszInterfaceGUID, 
                    pITF->pszInterfaceGUID, strlen(pITF->pszInterfaceGUID)) == 0)
        {
            // Entry is at head of list in table
            if (pITFTemp == g_ITFTable.pITFBuckets[dwIndex].pItf)
            {
                g_ITFTable.pITFBuckets[dwIndex].pItf = pITFTemp->pNext;
            }
            else
            {
                // Entry is inside list in table
                pITFWalker->pNext = pITFTemp->pNext;
            }
        
            break;
        }

        pITFWalker = pITFTemp;
        pITFTemp = pITFWalker->pNext;
    }

    return;
}


//
// ElNdisuioEnumerateInterfaces
// 
// Description:
//
// Function called to enumerate the interfaces on which NDISUIO is bound
//
// Arguments:
//      pBuffer - Pointer to buffer which will hold interface details
//      dwAvailableInterfaces - Number of interfaces for which details can
//                      be held in pItfBuffer
//      dwBufferSize - Number of bytes in pItfBuffer
//
// Return values:
// 

DWORD
ElNdisuioEnumerateInterfaces (
        IN OUT  PNDIS_ENUM_INTF     pItfBuffer,
        IN      DWORD               dwAvailableInterfaces,
        IN      DWORD               dwBufferSize
        )
{
    DWORD       dwDesiredAccess;
    DWORD       dwShareMode;
    LPSECURITY_ATTRIBUTES   lpSecurityAttributes = NULL;
    DWORD       dwCreationDistribution;
    DWORD       dwFlagsAndAttributes;
    HANDLE      hTemplateFile;
    HANDLE      hHandle;
    DWORD       dwBytesReturned = 0;
    INT         i;
    CHAR        Buf[1024];
    DWORD       BufLength = sizeof(Buf);
    DWORD       BytesWritten = 0;
    PNDISUIO_QUERY_BINDING pQueryBinding = NULL;
    PCHAR       pTempBuf = NULL;
    DWORD       dwRetCode = NO_ERROR;

    dwDesiredAccess = GENERIC_READ | GENERIC_WRITE;
    dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
    dwCreationDistribution = OPEN_EXISTING;
    dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED;
    hTemplateFile = (HANDLE)INVALID_HANDLE_VALUE;

    TRACE0 (DEVICE, "ElNdisuioEnumerateInterfaces: Opening handle");

    do 
    {

        hHandle = CreateFileA (
                    pNdisuioDevice,
                    dwDesiredAccess,
                    dwShareMode,
                    lpSecurityAttributes,
                    dwCreationDistribution,
                    0,
                    NULL
                    );

        if (hHandle == INVALID_HANDLE_VALUE)
        {
            dwRetCode = GetLastError();
            TRACE1 (DEVICE, "ElNdisuioEnumerateInterfaces: Failed in CreateFile with error %d", dwRetCode);

            break;
        }

        // Send IOCTL to ensure NDISUIO binds to all relevant interfaces

        if (!DeviceIoControl (
                    hHandle,
                    IOCTL_NDISUIO_BIND_WAIT,
                    NULL,
                    0,
                    NULL,
                    0,
                    &dwBytesReturned,
                    NULL))
        {
            dwRetCode = GetLastError();
            TRACE1 (DEVICE, "ElNdisuioEnumerateInterfaces: Failed in DeviceIoCoontrol NDISUIO_BIND_WAIT with error %d", dwRetCode);
            break;
        }
    
        pQueryBinding = (PNDISUIO_QUERY_BINDING)Buf;

        pTempBuf = (PBYTE)pItfBuffer + dwBufferSize;

        i = 0;
        for (pQueryBinding->BindingIndex = i;
            pQueryBinding->BindingIndex < dwAvailableInterfaces;
            pQueryBinding->BindingIndex = ++i)
        {

Try_Again:

            g_QueryBinding = NULL;
            g_TempBuf = NULL;
            g_ItfBuffer = NULL;
            g_Buf = NULL;
            g_ItfBufferSize = 0;
            g_BreakAt = 1;
            
            // Query for one interface at a time
            
            if (DeviceIoControl (
                    hHandle,
                    IOCTL_NDISUIO_QUERY_BINDING,
                    pQueryBinding,
                    sizeof(NDISUIO_QUERY_BINDING),
                    Buf,
                    BufLength,
                    &BytesWritten,
                    NULL))
            {
                TRACE3 (DEVICE, "NdisuioEnumerateInterfaces: NDISUIO bound to: %2d. %ws\n     - %ws\n",
                    pQueryBinding->BindingIndex,
                    (PUCHAR)pQueryBinding + pQueryBinding->DeviceNameOffset,
                    (PUCHAR)pQueryBinding + pQueryBinding->DeviceDescrOffset);

                g_QueryBinding = (PVOID)pQueryBinding;
                g_TempBuf = (PVOID)pTempBuf;
                g_ItfBuffer = (PVOID)pItfBuffer;
                g_ItfBufferSize = dwBufferSize;
                g_Buf = (PVOID)Buf;
                g_BreakAt = 1;

                pTempBuf = pTempBuf - ((pQueryBinding->DeviceNameLength + 7) & 0xfffffff8);

                if (((PBYTE)pTempBuf - (PBYTE)&pItfBuffer->Interface[pItfBuffer->TotalInterfaces]) <= 0)
                {
                    // Going beyond start of buffer, Error
                    TRACE0 (DEVICE, "NdisuioEnumerateInterfaces: DeviceName: Memory being corrupted !!!");
                    DebugBreak ();

                    goto Try_Again;
                }

                pItfBuffer->Interface[pItfBuffer->TotalInterfaces].DeviceName.Buffer = (PWCHAR) pTempBuf;


                memcpy ((BYTE *)(pItfBuffer->Interface[pItfBuffer->TotalInterfaces].DeviceName.Buffer), (BYTE *)((PUCHAR)pQueryBinding + pQueryBinding->DeviceNameOffset), (pQueryBinding->DeviceNameLength - sizeof(WCHAR)));
                pItfBuffer->Interface[pItfBuffer->TotalInterfaces].DeviceName.Length = (SHORT) ( pQueryBinding->DeviceNameLength - sizeof(WCHAR));
                pItfBuffer->Interface[pItfBuffer->TotalInterfaces].DeviceName.MaximumLength = (SHORT) ( pQueryBinding->DeviceNameLength - sizeof(WCHAR));

                pTempBuf = pTempBuf - ((pQueryBinding->DeviceDescrLength + 7) & 0xfffffff8);;
                g_BreakAt = 2;

                if (((PBYTE)pTempBuf - (PBYTE)&pItfBuffer->Interface[pItfBuffer->TotalInterfaces]) <= 0)
                {
                    // Going beyond start of buffer, Error
                    TRACE0 (DEVICE, "NdisuioEnumerateInterfaces: DeviceDescr: Memory being corrupted !!!");
                    DebugBreak ();
                    goto Try_Again;
                }

                pItfBuffer->Interface[pItfBuffer->TotalInterfaces].DeviceDescription.Buffer = (PWCHAR) pTempBuf;


                memcpy ((pItfBuffer->Interface[pItfBuffer->TotalInterfaces].DeviceDescription.Buffer), (PWCHAR)((PUCHAR)pQueryBinding + pQueryBinding->DeviceDescrOffset), (pQueryBinding->DeviceDescrLength - sizeof(WCHAR)));
                pItfBuffer->Interface[pItfBuffer->TotalInterfaces].DeviceDescription.Length = (SHORT) (pQueryBinding->DeviceDescrLength - sizeof(WCHAR));
                pItfBuffer->Interface[pItfBuffer->TotalInterfaces].DeviceDescription.MaximumLength = (SHORT) (pQueryBinding->DeviceDescrLength - sizeof(WCHAR));

                pItfBuffer->TotalInterfaces++;

                memset(Buf, 0, BufLength);
            }
            else
            {
                dwRetCode = GetLastError ();
                if (dwRetCode != ERROR_NO_MORE_ITEMS)
                {
                    TRACE1 (DEVICE, "ElNdisuioEnumerateInterfaces: DeviceIoControl terminated for with IOCTL_NDISUIO_QUERY_BINDING with error %ld",
                            dwRetCode);
                }
                else
                {
                    // Reset error, since it only indicates end-of-list
                    dwRetCode = NO_ERROR;
                    TRACE0 (DEVICE, "ElNdisuioEnumerateInterfaces: DeviceIoControl IOCTL_NDISUIO_QUERY_BINDING has no more entries");
                }
                break;
            }
        }
            
    } while (FALSE);

    // Cleanup 

    if (hHandle != INVALID_HANDLE_VALUE)
    {
        if (!CloseHandle(hHandle))
        {
            dwRetCode = GetLastError();
            TRACE1 (DEVICE, "ElNdisuioEnumerateInterfaces: Error in CloseHandle %d", dwRetCode);
        }
    }
         
    TRACE2 (DEVICE, "ElNdisuioEnumerateInterfaces: Opened handle %p with dwRetCode %d", 
            hHandle, dwRetCode);

    return dwRetCode;
}



//
// ElShutdownInterface
// 
// Description:
//
// Function called to stop EAPOL state machine, close handle to NDISUIO and
//  remove existence of the interface from the module
//
// Arguments:
//      pszGUID - Pointer to interface GUID
//
// Return values:
//  NO_ERROR - success
//  non-zero - error
// 

DWORD
ElShutdownInterface (
        IN      CHAR               *pszGUID
        )
{
    EAPOL_PCB   *pPCB = NULL;
    EAPOL_ITF   *pITF = NULL;
    HANDLE      hDevice = NULL;
    DWORD       dwRetCode = NO_ERROR;

    do
    {

        TRACE0(DEVICE, "ElShutdownInterface: Called for interface removal");

        ACQUIRE_WRITE_LOCK (&(g_ITFLock));

        // Check if EAPOL was actually started on this interface
        // Verify by checking existence of corresponding 
        // entry in hash table
    
        ACQUIRE_WRITE_LOCK (&(g_PCBLock));
        if ((pPCB = ElGetPCBPointerFromPortGUID(pszGUID))
                != NULL)
        {
            RELEASE_WRITE_LOCK (&(g_PCBLock));
            TRACE0 (DEVICE, "ElShutdownInterface: Found PCB entry for interface");
    
            if ((pITF = ElGetITFPointerFromInterfaceDesc(
                            pPCB->pszFriendlyName))
                            == NULL)
            {
                TRACE0 (DEVICE, "ElShutdownInterface: Did not find ITF entry when PCB exits, HOW BIZARRE !!!");
            }
    
            if ((dwRetCode = ElDeletePort (
                            pszGUID, 
                            &hDevice)) != NO_ERROR)
            {
                TRACE1 (DEVICE, "ElShutdownInterface: Error in deleting port for %s", 
                    pPCB->pszDeviceGUID);
            }
    
            // Remove interface entry from interface table
            
            if (pITF != NULL)
            {
                ElRemoveITFFromTable(pITF);
                        
                if (pITF->pszInterfaceDesc)
                {
                    FREE (pITF->pszInterfaceDesc);
                }
                if (pITF->pszInterfaceGUID)
                {
                    FREE (pITF->pszInterfaceGUID);
                }
                if (pITF)
                {
                    FREE (pITF);
                }
            }
    
            // Close the handle to the NDISUIO driver

            if (hDevice != NULL)
            {
                if ((dwRetCode = ElCloseInterfaceHandle (hDevice)) 
                        != NO_ERROR)
                {
                    TRACE1 (DEVICE, 
                        "ElShutdownInterface: Error in ElCloseInterfaceHandle %d", 
                        dwRetCode);
                }
            }
    
            TRACE1 (DEVICE, "ElShutdownInterface: Port deleted %s", 
                    pszGUID);
    
        }
        else
        {
            RELEASE_WRITE_LOCK (&(g_PCBLock));

            // Ignore device removal 
            
            TRACE0 (DEVICE, "ElShutdownInterface: ElGetPCBPointerFromPortGUID did not find any matching entry, ignoring interface REMOVAL");
    
        }
    
        RELEASE_WRITE_LOCK (&g_ITFLock);

    } while (FALSE);

    return dwRetCode;
}