/*++

Copyright (c) 1999, Microsoft Corporation

Module Name:

    elprotocol.c

Abstract:
    This module implements functions related to EAPOL 
    protocol


Revision History:

    sachins, Apr 30 2000, Created

--*/

#include "pcheapol.h"
#pragma hdrstop


//
// ElProcessReceivedPacket
//
// Description:
//
//      Function called to process data received from the NDISUIO driver.
//      The EAPOL packet is extracted and further processing is done.
//
//
// Arguments:
//      pvContext - Context buffer which is a pointer to EAPOL_BUFFER structure
//
// Return Values:
//

DWORD
WINAPI
ElProcessReceivedPacket (
        IN  PVOID   pvContext
        )
{
    EAPOL_PCB       *pPCB = NULL;
    EAPOL_BUFFER    *pEapolBuffer = NULL;
    DWORD           dwLength = 0;
    ETH_HEADER      *pEthHdr = NULL;
    EAPOL_PACKET    *pEapolPkt = NULL;
    DWORD           dw8021PSize = 0;
    PPP_EAP_PACKET  *pEapPkt = NULL;
    BYTE            *pBuffer;
    BOOLEAN         ReqId = FALSE;      // EAPOL state machine local variables
    BOOLEAN         ReqAuth = FALSE;
    BOOLEAN         EapSuccess = FALSE;
    BOOLEAN         EapFail = FALSE;
    BOOLEAN         RxKey = FALSE;
    GUID            DeviceGuid;
    DWORD           dwRetCode = NO_ERROR;


    if (pvContext == NULL)
    {
        TRACE0 (EAPOL, "ProcessReceivedPacket: Critical error, Context is NULL");
        return 0;
    }

    pEapolBuffer = (EAPOL_BUFFER *)pvContext;
    pPCB = (EAPOL_PCB *)pEapolBuffer->pvContext;
    dwLength = pEapolBuffer->dwBytesTransferred;
    pBuffer = (BYTE *)pEapolBuffer->pBuffer;

    TRACE1 (EAPOL, "ProcessReceivedPacket entered, length = %ld", dwLength);

    ElParsePacket (pBuffer, dwLength, TRUE);

        
    ACQUIRE_WRITE_LOCK (&(pPCB->rwLock));

    do 
    {
        // The Port was verified to be active before the workitem
        // was queued. But do a double-check

        // Validate packet length
        // Should be atleast ETH_HEADER and first 4 required bytes of 
        // EAPOL_PACKET
        if (dwLength < (sizeof(ETH_HEADER) + 4))
        {
            TRACE2 (EAPOL, "ProcessReceivedPacket: Packet length %ld is less than minimum required %d. Ignoring packet",
                    dwLength, (sizeof(ETH_HEADER) + 4));
            dwRetCode =  ERROR_INVALID_PACKET_LENGTH_OR_ID;
            break;
        }

        // If the source address is same as the local MAC address, it is a 
        // multicast packet copy sent out being received
        pEthHdr = (ETH_HEADER *)pBuffer;
        if ((memcmp ((BYTE *)pEthHdr->bSrcAddr, 
                        (BYTE *)pPCB->bSrcMacAddr, 
                        SIZE_MAC_ADDR)) == 0)
        {
            TRACE0 (EAPOL, "ProcessReceivedPacket: Src MAC address of packet matches local address. Ignoring packet");
            dwRetCode = ERROR_INVALID_ADDRESS;
            break;
        }

        // Verify if the packet contains a 802.1P tag. If so, skip the 4 bytes
        // after the src+dest mac addresses

        if ((WireToHostFormat16(pBuffer + sizeof(ETH_HEADER)) == EAPOL_8021P_TAG_TYPE))
        {
            pEapolPkt = (EAPOL_PACKET *)(pBuffer + sizeof(ETH_HEADER) + 4);
            dw8021PSize = 4;
        }
        else
        {
            pEapolPkt = (EAPOL_PACKET *)(pBuffer + sizeof(ETH_HEADER));
        }

        // Validate Ethernet type in the incoming packet
        // It should be the same as the one defined for the
        // current port

        if (memcmp ((BYTE *)pEapolPkt->EthernetType, (BYTE *)pPCB->bEtherType,
                        SIZE_ETHERNET_TYPE) != 0)
        {
            TRACE0 (EAPOL, "ProcessReceivedPacket: Packet Ethernet type does not match expected type. Ignoring packet");
            TRACE0 (EAPOL, "Incoming:");
            EAPOL_DUMPBA ((BYTE *)pEapolPkt->EthernetType, SIZE_ETHERNET_TYPE);
            TRACE0 (EAPOL, "Expected:");
            EAPOL_DUMPBA ((BYTE *)pPCB->bEtherType, SIZE_ETHERNET_TYPE);
            dwRetCode = ERROR_INVALID_PACKET_LENGTH_OR_ID;
            break;
        }

        // EAPOL packet type should be valid
        if ((pEapolPkt->PacketType != EAP_Packet) &&
                (pEapolPkt->PacketType != EAPOL_Start) &&
                (pEapolPkt->PacketType != EAPOL_Logoff) &&
                (pEapolPkt->PacketType != EAPOL_Key))
        {
            TRACE1 (EAPOL, "ProcessReceivedPacket: Invalid EAPOL packet type %d. Ignoring packet",
                    pEapolPkt->PacketType);
            dwRetCode = ERROR_INVALID_PACKET;
            break;
        }


        if ((WireToHostFormat16(pEapolPkt->PacketBodyLength) > (MAX_PACKET_SIZE  - (SIZE_ETHERNET_CRC + sizeof(ETH_HEADER) + dw8021PSize + FIELD_OFFSET (EAPOL_PACKET, PacketBody)))))
           //  ||
                // (WireToHostFormat16(pEapolPkt->PacketBodyLength) != (dwLength - (sizeof(ETH_HEADER) + dw8021PSize + FIELD_OFFSET (EAPOL_PACKET, PacketBody)))))
        {
            TRACE3 (EAPOL, "ProcessReceivedPacket: Invalid length in EAPOL packet (%ld), Max length (%ld), Exact length (%ld), Ignoring packet",
                    WireToHostFormat16(pEapolPkt->PacketBodyLength),
                    (MAX_PACKET_SIZE - (SIZE_ETHERNET_CRC + sizeof(ETH_HEADER) + dw8021PSize + FIELD_OFFSET (EAPOL_PACKET, PacketBody))),
                    (dwLength - (sizeof(ETH_HEADER) + dw8021PSize + FIELD_OFFSET (EAPOL_PACKET, PacketBody)))
                    );
            dwRetCode = ERROR_INVALID_PACKET;
            break;
        }

        // Determine the value of local EAPOL state variables
        if (pEapolPkt->PacketType == EAP_Packet)
        {
            TRACE0 (EAPOL, "ProcessReceivedPacket: EAP_Packet");
            // Validate length of packet for EAP
            // Should be atleast (ETH_HEADER+EAPOL_PACKET)
            if (dwLength < (sizeof (ETH_HEADER) + dw8021PSize + FIELD_OFFSET (EAPOL_PACKET, PacketBody) + FIELD_OFFSET(PPP_EAP_PACKET, Data)))
            {
                TRACE1 (EAPOL, "ProcessReceivedPacket: Invalid length of EAP packet %d. Ignoring packet",
                        dwLength);
                dwRetCode = ERROR_INVALID_PACKET;
                break;
            }


            pEapPkt = (PPP_EAP_PACKET *)pEapolPkt->PacketBody;

            if (WireToHostFormat16(pEapolPkt->PacketBodyLength) != WireToHostFormat16 (pEapPkt->Length))
            {
                TRACE2 (EAPOL, "ProcessReceivedPacket: Invalid length in EAPOL packet (%ld) not matching EAP length (%ld), Ignoring packet",
                        WireToHostFormat16(pEapolPkt->PacketBodyLength),
                        WireToHostFormat16 (pEapPkt->Length));
                dwRetCode = ERROR_INVALID_PACKET;
                break;
            }

            if (pEapPkt->Code == EAPCODE_Request)
            {
                // Validate length of packet for EAP-Request packet
                // Should be atleast (ETH_HEADER+EAPOL_PACKET-1+PPP_EAP_PACKET)
                if (dwLength < (sizeof (ETH_HEADER) + sizeof(EAPOL_PACKET)-1
                            + sizeof (PPP_EAP_PACKET)))
                {
                    TRACE1 (EAPOL, "ProcessReceivedPacket: Invalid length of EAP Request packet %d. Ignoring packet",
                            dwLength);
                    dwRetCode = ERROR_INVALID_PACKET;
                    break;
                }
                if (pEapPkt->Data[0] == EAPTYPE_Identity)
                {
                    pPCB->fIsRemoteEndEAPOLAware = TRUE;

                    switch (pPCB->dwSupplicantMode)
                    {
                        case SUPPLICANT_MODE_0:
                        case SUPPLICANT_MODE_1:
                            // ignore
                            break;
                        case SUPPLICANT_MODE_2:
                        case SUPPLICANT_MODE_3:
                            pPCB->fEAPOLTransmissionFlag = TRUE;
                            break;
                    }

                    ReqId = TRUE;
                }
                else
                {
                    ReqAuth = TRUE;
                }
            }
            else if (pEapPkt->Code ==  EAPCODE_Success)
            {
                EapSuccess = TRUE;
            }
            else if (pEapPkt->Code == EAPCODE_Failure)
            {
                EapFail = TRUE;
            }
            else
            {
                // Invalid type
                TRACE1 (EAPOL, "ProcessReceivedPacket: Invalid EAP packet type %d. Ignoring packet",
                        pEapPkt->Code);
                dwRetCode = ERROR_INVALID_PACKET;
                break;
            }
        }
        else
        {
            TRACE0 (EAPOL, "ProcessReceivedPacket: != EAP_Packet");
            if (pEapolPkt->PacketType == EAPOL_Key)
            {
                TRACE0 (EAPOL, "ProcessReceivedPacket: == EAPOL_Key");
                RxKey = TRUE;
            
            }
            else
            {
                TRACE0 (EAPOL, "ProcessReceivedPacket: Invalid packet type");
            }
        }

        // State machine does not accept packets for inactive/disabled ports
        if (!EAPOL_PORT_ACTIVE(pPCB))
        {
            TRACE1 (EAPOL, "ProcessReceivedPacket: Port %ws not active",
                    pPCB->pwszDeviceGUID);
            if (EAPOL_PORT_DISABLED(pPCB))
            {
                DbLogPCBEvent (DBLOG_CATEG_WARN, pPCB, EAPOL_NOT_ENABLED_PACKET_REJECTED);
            }
            break;
        }

        if (RxKey)
        {
            if ((dwRetCode = FSMKeyReceive (pPCB,
                            pEapolPkt)) != NO_ERROR)
            {
                break;
            }
        }

        switch (pPCB->State)
        {
            // ReqId, ReqAuth, EapSuccess, EapFail, RxKey are inherently 
            // mutually exclusive
            // No checks will be made to verify this
            // Also, assumption is being made that in any state, maximum 
            // one timer may be active on the port.

            case EAPOLSTATE_LOGOFF:
                // Only a User Logon event can get the port out of
                // LOGOFF state
                TRACE0 (EAPOL, "ProcessReceivedPacket: LOGOFF state, Ignoring packet");
                break;

            case EAPOLSTATE_DISCONNECTED:
                // Only a Media Connect / User logon / System reset event 
                // can get the port out of DISCONNECTED state
                TRACE0 (EAPOL, "ProcessReceivedPacket: DISCONNECTED state, Ignoring packet");
                break;

            case EAPOLSTATE_CONNECTING:
                TRACE0 (EAPOL, "ProcessReceivedPacket: EAPOLSTATE_CONNECTING");

                if (EapSuccess)
                {
                    if (!pPCB->fLocalEAPAuthSuccess)
                    {
                        TRACE0 (EAPOL, "ProcessReceivedPacket: Dropping invalid EAP-Success packet");
                        dwRetCode = ERROR_INVALID_PACKET;
                        break;
                    }
                }

                if (ReqId | EapSuccess | EapFail)
                {
                    // Deactivate current timer
                    RESTART_TIMER (pPCB->hTimer,
                            INFINITE_SECONDS, 
                            "PCB",
                            &dwRetCode);
                    if (dwRetCode != NO_ERROR)
                    {
                        break;
                    }
                }

                if (EapSuccess)
                {
                    if ((dwRetCode = ElProcessEapSuccess (pPCB,
                                                    pEapolPkt)) != NO_ERROR)
                    {
                        break;
                    }
                }
                else
                if (EapFail)
                {
                    if ((dwRetCode = ElProcessEapFail (pPCB,
                                                pEapolPkt)) != NO_ERROR)
                    {
                        break;
                    }
                }
                else
                if (ReqId)
                {
                    if ((dwRetCode = FSMAcquired (pPCB,
                                                    pEapolPkt)) != NO_ERROR)
                    {
                        break;
                    }
                }


                break;

            case EAPOLSTATE_ACQUIRED:
                TRACE0 (EAPOL, "ProcessReceivedPacket: EAPOLSTATE_ACQUIRED");
                if (EapSuccess)
                {
                    if (!pPCB->fLocalEAPAuthSuccess)
                    {
                        TRACE0 (EAPOL, "ProcessReceivedPacket: Dropping invalid EAP-Success packet");
                        dwRetCode = ERROR_INVALID_PACKET;
                        break;
                    }
                }

                if (ReqId | ReqAuth | EapSuccess | EapFail)
                {
                    // Deactivate current timer
                    RESTART_TIMER (pPCB->hTimer,
                            INFINITE_SECONDS,  
                            "PCB",
                            &dwRetCode);
                    if (dwRetCode != NO_ERROR)
                    {
                        break;
                    }

                    // Reset EapUI state
                    if (!ReqId)
                    {
                        pPCB->EapUIState &= ~EAPUISTATE_WAITING_FOR_IDENTITY;
                    }
                }

                if (EapSuccess)
                {
                    if ((dwRetCode = ElProcessEapSuccess (pPCB,
                                                    pEapolPkt)) != NO_ERROR)
                    {
                        break;
                    }
                }
                else
                if (EapFail)
                {
                    if ((dwRetCode = ElProcessEapFail (pPCB,
                                                pEapolPkt)) != NO_ERROR)
                    {
                        break;
                    }
                }
                else
                if (ReqId)
                {
                    if ((dwRetCode = FSMAcquired (pPCB,
                                                pEapolPkt)) != NO_ERROR)
                    {
                        break;
                    }
                }
                else
                if (ReqAuth)
                {
                    if ((dwRetCode = FSMAuthenticating (pPCB,
                                                pEapolPkt)) != NO_ERROR)
                    {
                        break;
                    }
                }

                break;

            case EAPOLSTATE_AUTHENTICATING:
                TRACE0 (EAPOL, "ProcessReceivedPacket: EAPOLSTATE_AUTHENTICATING");
                // Common timer deletion
                if (ReqAuth | ReqId | EapSuccess | EapFail)
                {
                    // Deactivate current timer
                    RESTART_TIMER (pPCB->hTimer,
                            INFINITE_SECONDS,   
                            "PCB",
                            &dwRetCode);
                    if (dwRetCode != NO_ERROR)
                    {
                        break;
                    }

                    if (ReqId)
                    {
                        if ((dwRetCode = FSMAcquired (pPCB,
                                                    pEapolPkt)) != NO_ERROR)
                        {
                            break;
                        }
                    }
                    else
                    {
                        if ((dwRetCode = FSMAuthenticating (pPCB,
                                                    pEapolPkt)) != NO_ERROR)
                        {
                            break;
                        }
                    }

                    // Reset EapUI state
                    if (!ReqAuth)
                    {
                        pPCB->EapUIState &= ~EAPUISTATE_WAITING_FOR_UI_RESPONSE;
                    }
                }

                // Continue further processing

                if (EapSuccess | EapFail)
                {
                    if (EapSuccess)
                    {
                        if (!pPCB->fLocalEAPAuthSuccess)
                        {
                            TRACE0 (EAPOL, "ProcessReceivedPacket: Dropping invalid EAP-Success packet");
                            dwRetCode = ERROR_INVALID_PACKET;
                            break;
                        }
                    }

                    // Auth timer will have restarted in FSMAuthenticating
                    // Deactivate the timer
                    RESTART_TIMER (pPCB->hTimer,
                            INFINITE_SECONDS,
                            "PCB",
                            &dwRetCode);
                    if (dwRetCode != NO_ERROR)
                    {
                        break;
                    }

                    // If the packet received was a EAP-Success, go into 
                    // AUTHENTICATED state
                    if (EapSuccess)
                    {
                        if ((dwRetCode = ElProcessEapSuccess (pPCB,
                                                    pEapolPkt)) != NO_ERROR)
                        {
                            break;
                        }
    
                    }
                    else
                    // If the packet received was a EAP-Failure, go into 
                    // HELD state
                    if (EapFail)
                    {
                        if ((dwRetCode = ElProcessEapFail (pPCB,
                                                pEapolPkt)) != NO_ERROR)
                        {
                            break;
                        }
                    }
                }

                break;

            case EAPOLSTATE_HELD:
                TRACE0 (EAPOL, "ProcessReceivedPacket: HELD state, Ignoring packet");
                if (ReqId)
                {
                    // Deactivate current timer
                    RESTART_TIMER (pPCB->hTimer,
                            INFINITE_SECONDS,
                            "PCB",
                            &dwRetCode);
                    if (dwRetCode != NO_ERROR)
                    {
                        break;
                    }
                    if ((dwRetCode = FSMAcquired (pPCB,
                                                pEapolPkt)) != NO_ERROR)
                    {
                        break;
                    }
                }
                break;

            case EAPOLSTATE_AUTHENTICATED:
                TRACE0 (EAPOL, "ProcessReceivedPacket: STATE_AUTHENTICATED");
                if (ReqId)
                {
                    if ((dwRetCode = FSMAcquired (pPCB,
                                                pEapolPkt)) != NO_ERROR)
                    {
                        break;
                    }

                }
                else
                {
                    if (EapFail)
                    {
                        if ((dwRetCode = ElProcessEapFail (pPCB,
                                                pEapolPkt)) != NO_ERROR)
                        {
                            break;
                        }
                    }
                }
                break;

            default:
                TRACE0 (EAPOL, "ProcessReceivedPacket: Critical Error. Invalid state, Ignoring packet");
                break;
        }

    } while (FALSE);

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

    // Post a new read request, ignoring errors
            
    if (EAPOL_PORT_DELETED(pPCB))
    {
        TRACE1 (EAPOL, "ProcessReceivedPacket: Port %ws deleted, not reposting read request",
                pPCB->pwszDeviceGUID);
    }
    else
    {
        TRACE1 (EAPOL, "ProcessReceivedPacket: Reposting buffer on port %ws",
                pPCB->pwszDeviceGUID);
        
        // ElReadFromPort creates a new context buffer, adds a ref count,
        // and posts the read request
        if ((dwRetCode = ElReadFromPort (
                                        pPCB,
                                        NULL,
                                        0
                                        )) != NO_ERROR)
        {
            TRACE1 (EAPOL, "ProcessReceivedPacket: Critical error: ElReadFromPort error %d",
                    dwRetCode);
        }
    }

    RELEASE_WRITE_LOCK (&(pPCB->rwLock));

    TRACE2 (EAPOL, "ProcessReceivedPacket: pPCB= %p, RefCnt = %ld", 
            pPCB, pPCB->dwRefCount);

    // Dereference ref count held for the read that was just processed
    EAPOL_DEREFERENCE_PORT(pPCB);

    TRACE0 (EAPOL, "ProcessReceivedPacket exit");
    
    InterlockedDecrement (&g_lWorkerThreads);

    return 0;
}


// 
// FSMDisconnected
//
// Description:
//      Function called when media disconnect occurs
//
// Arguments:
//      pPCB - Pointer to PCB for the port on which media disconnect occurs
//
// Return values:
//      NO_ERROR - success
//      non-zero - error
//

DWORD
FSMDisconnected (
        IN  EAPOL_PCB       *pPCB,
        IN  EAPOL_PACKET    *pEapolPkt
        )
{
    DWORD           dwRetCode   = NO_ERROR;

    TRACE1 (EAPOL, "FSMDisconnected entered for port %ws", pPCB->pwszFriendlyName);

    do 
    {

    } while (FALSE);

    TRACE1 (EAPOL, "Setting state DISCONNECTED for port %ws", pPCB->pwszFriendlyName);

    DbLogPCBEvent (DBLOG_CATEG_INFO, pPCB, EAPOL_STATE_TRANSITION, 
            EAPOLStates[((pPCB->State < EAPOLSTATE_LOGOFF) || (pPCB->State > EAPOLSTATE_AUTHENTICATED))?EAPOLSTATE_UNDEFINED:pPCB->State], 
            EAPOLStates[EAPOLSTATE_DISCONNECTED]);

    pPCB->State = EAPOLSTATE_DISCONNECTED;

    pPCB->EapUIState = 0;

    // Free Identity buffer

    if (pPCB->pszIdentity != NULL)
    {
        FREE (pPCB->pszIdentity);
        pPCB->pszIdentity = NULL;
    }

    // Free Password buffer

    if (pPCB->PasswordBlob.pbData != NULL)
    {
        FREE (pPCB->PasswordBlob.pbData);
        pPCB->PasswordBlob.pbData = NULL;
        pPCB->PasswordBlob.cbData = 0;
    }

    // Free user-specific data in the PCB

    if (pPCB->pCustomAuthUserData != NULL)
    {
        FREE (pPCB->pCustomAuthUserData);
        pPCB->pCustomAuthUserData = NULL;
    }

    // Free connection data, though it is common to all users

    if (pPCB->pCustomAuthConnData != NULL)
    {
        FREE (pPCB->pCustomAuthConnData);
        pPCB->pCustomAuthConnData = NULL;
    }

    pPCB->dwAuthFailCount = 0;

    pPCB->fGotUserIdentity = FALSE;

    if (pPCB->hUserToken != NULL)
    {
        if (!CloseHandle (pPCB->hUserToken))
        {
            dwRetCode = GetLastError ();
            TRACE1 (EAPOL, "FSMDisconnected: CloseHandle failed with error %ld",
                dwRetCode);
            dwRetCode = NO_ERROR;
        }
    }
    pPCB->hUserToken = NULL;

    TRACE1 (EAPOL, "FSMDisconnected completed for port %ws", pPCB->pwszFriendlyName);

    return dwRetCode;
}


// 
// FSMLogoff
//
// Description:
//      Function called to send out EAPOL_Logoff packet. Usually triggered by
//      user logging off.
//
// Arguments:
//      pPCB - Pointer to PCB for the port on which logoff packet is to be
//              sent out
//
// Return values:
//      NO_ERROR - success
//      non-zero - error
//

DWORD
FSMLogoff (
        IN  EAPOL_PCB       *pPCB,
        IN  EAPOL_PACKET    *pDummy
        )
{
    EAPOL_PACKET    *pEapolPkt  = NULL;
    BOOLEAN         fAuthSendPacket = FALSE;
    BOOLEAN         fSupplicantSendPacket = FALSE;
    DWORD           dwRetCode   = NO_ERROR;

    TRACE1 (EAPOL, "FSMLogoff entered for port %ws", pPCB->pwszFriendlyName);

    do 
    {
        // End EAP session
        ElEapEnd (pPCB);

        // Send out EAPOL_Logoff conditionally

        if ( ((pPCB->dwSupplicantMode == SUPPLICANT_MODE_2) &&
                (pPCB->fEAPOLTransmissionFlag)) || 
                (pPCB->dwSupplicantMode == SUPPLICANT_MODE_3))
        {
            fSupplicantSendPacket = TRUE;
        }

        switch (pPCB->dwEAPOLAuthMode)
        {
            case EAPOL_AUTH_MODE_0:
                fAuthSendPacket = TRUE;
                break;

            case EAPOL_AUTH_MODE_1:
                fAuthSendPacket = FALSE;
                break;

            case EAPOL_AUTH_MODE_2:
                fAuthSendPacket = FALSE;
                break;
        }

        if ((fSupplicantSendPacket) && (fAuthSendPacket))
        {

        // Allocate new buffer
        pEapolPkt = (EAPOL_PACKET *) MALLOC (sizeof (EAPOL_PACKET));
        if (pEapolPkt == NULL)
        {
            TRACE0 (EAPOL, "FSMLogoff: Error in allocating memory for EAPOL packet");
            dwRetCode = ERROR_NOT_ENOUGH_MEMORY;
            break;
        }

        // Fill in fields
        memcpy ((BYTE *)pEapolPkt->EthernetType, 
                (BYTE *)pPCB->bEtherType, 
                SIZE_ETHERNET_TYPE);
        pEapolPkt->ProtocolVersion = pPCB->bProtocolVersion;
        pEapolPkt->PacketType = EAPOL_Logoff;
        HostToWireFormat16 ((WORD)0, (BYTE *)pEapolPkt->PacketBodyLength);

        // Send packet out on the port
        dwRetCode = ElWriteToPort (pPCB,
                                    (CHAR *)pEapolPkt,
                                    sizeof (EAPOL_PACKET));
        if (dwRetCode != NO_ERROR)
        {
            TRACE1 (EAPOL, "FSMLogoff: Error in writing Logoff pkt to port %ld",
                    dwRetCode);
            break;
        }

        // Mark that EAPOL_Logoff was sent out on the port
        pPCB->dwLogoffSent = 1;

        }

    } while (FALSE);

    TRACE1 (EAPOL, "Setting state LOGOFF for port %ws", pPCB->pwszFriendlyName);

    DbLogPCBEvent (DBLOG_CATEG_INFO, pPCB, EAPOL_STATE_TRANSITION, 
            EAPOLStates[((pPCB->State < EAPOLSTATE_LOGOFF) || (pPCB->State > EAPOLSTATE_AUTHENTICATED))?EAPOLSTATE_UNDEFINED:pPCB->State], 
            EAPOLStates[EAPOLSTATE_LOGOFF]);

    pPCB->State = EAPOLSTATE_LOGOFF;

    pPCB->EapUIState = 0;

    // Release user token
    if (pPCB->hUserToken != NULL)
    {
        if (!CloseHandle (pPCB->hUserToken))
        {
            dwRetCode = GetLastError ();
            TRACE1 (EAPOL, "FSMLogoff: CloseHandle failed with error %ld",
                    dwRetCode);
            dwRetCode = NO_ERROR;
        }
    }
    pPCB->hUserToken = NULL;

    // Free Identity buffer

    if (pPCB->pszIdentity != NULL)
    {
        FREE (pPCB->pszIdentity);
        pPCB->pszIdentity = NULL;
    }

    // Free Password buffer

    if (pPCB->PasswordBlob.pbData != NULL)
    {
        FREE (pPCB->PasswordBlob.pbData);
        pPCB->PasswordBlob.pbData = NULL;
        pPCB->PasswordBlob.cbData = 0;
    }

    // Free user-specific data in the PCB

    if (pPCB->pCustomAuthUserData != NULL)
    {
        FREE (pPCB->pCustomAuthUserData);
        pPCB->pCustomAuthUserData = NULL;
    }

    pPCB->fGotUserIdentity = FALSE;

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

    TRACE1 (EAPOL, "FSMLogoff completed for port %ws", pPCB->pwszFriendlyName);

    return dwRetCode;
}


//
// FSMConnecting
//
// Description:
//
// Funtion called to send out EAPOL_Start packet. If MaxStart EAPOL_Start 
// packets have been sent out, State Machine moves to Authenticated state
//
// Arguments:
//      pPCB - Pointer to the PCB for the port on which Start packet is 
//      to be sent out
//
// Return values:
//      NO_ERROR - success
//      non-zero - error
//

DWORD
FSMConnecting (
        IN  EAPOL_PCB       *pPCB,
        IN  EAPOL_PACKET    *pDummy
        )
{
    EAPOL_PACKET    *pEapolPkt = NULL;
    DWORD           dwStartInterval = 0;               
    GUID            DeviceGuid;
    DWORD           dwRetCode = NO_ERROR;

    TRACE1 (EAPOL, "FSMConnecting entered for port %ws", pPCB->pwszFriendlyName);

    do 
    {
        // Flag that authentication has not completed in the EAP module
        // on the client-side.
        pPCB->fLocalEAPAuthSuccess = FALSE;
        pPCB->dwLocalEAPAuthResult = NO_ERROR;

        if (pPCB->State == EAPOLSTATE_CONNECTING)
        {
            // If PCB->State was Connecting earlier, increment ulStartCount 
            // else set ulStartCount to zero
    
            // Did not receive Req/Id
            if ((++(pPCB->ulStartCount)) > pPCB->EapolConfig.dwmaxStart)
            {
                // Deactivate start timer
                RESTART_TIMER (pPCB->hTimer,
                        INFINITE_SECONDS,
                        "PCB",
                        &dwRetCode);
                if (dwRetCode != NO_ERROR)
                {
                    break;
                }

                TRACE0 (EAPOL, "FSMConnecting: Sent out maxStart with no response, Setting AUTHENTICATED state");

                // Sent out enough EAPOL_Starts
                // Go into authenticated state
                if ((dwRetCode = FSMAuthenticated (pPCB,
                                            pEapolPkt)) != NO_ERROR)
                {
                    TRACE1 (EAPOL, "FSMConnecting: Error in FSMAuthenticated %ld",
                            dwRetCode);
                    break;
                }

                // No need to send out more EAPOL_Start packets

                // Reset start packet count
                pPCB->ulStartCount = 0;
                pPCB->fIsRemoteEndEAPOLAware = FALSE;
                break;
            }
        }
        else
        {
            pPCB->ulStartCount++;
        }
            
        // Initialize the address of previously associated AP
        // Only if the reauthentication goes through without getting
        // into CONNECTING state, will a IP Renew *not* be done
        ZeroMemory (pPCB->bPreviousDestMacAddr, SIZE_MAC_ADDR);

        // If user is not logged in, send out EAPOL_Start packets
        // at intervals of 1 second each. This is used to detect if the
        // interface is on a secure network or not. 
        // If user is logged in, use the configured value for the 
        // StartPeriod as the interval

        if (!g_fUserLoggedOn)
        {
            dwStartInterval = EAPOL_INIT_START_PERIOD; // 1 second
        }
        else
        {
            dwStartInterval = pPCB->EapolConfig.dwstartPeriod;
        }

        // Restart timer with startPeriod
        // Even if error occurs, timeout will happen
        // Else, we won't be able to get out of connecting state
        RESTART_TIMER (pPCB->hTimer,
                dwStartInterval,
                "PCB",
                &dwRetCode);
            
        if (dwRetCode != NO_ERROR)
        {
            TRACE1 (EAPOL, "FSMConnecting: Error in RESTART_TIMER %ld",
                    dwRetCode);
            break;
        }

        // Send out EAPOL_Start conditionally

        if (((pPCB->dwSupplicantMode == SUPPLICANT_MODE_2) &&
                (pPCB->fEAPOLTransmissionFlag)) || 
                (pPCB->dwSupplicantMode == SUPPLICANT_MODE_3))
        {

        // Send out EAPOL_Start
        // Allocate new buffer
        pEapolPkt = (EAPOL_PACKET *) MALLOC (sizeof(EAPOL_PACKET));
        if (pEapolPkt == NULL)
        {
            TRACE0 (EAPOL, "FSMConnecting: Error in allocating memory for EAPOL packet");
            dwRetCode = ERROR_NOT_ENOUGH_MEMORY;
            break;
        }

        memcpy ((BYTE *)pEapolPkt->EthernetType, 
                (BYTE *)pPCB->bEtherType, 
                SIZE_ETHERNET_TYPE);
        pEapolPkt->ProtocolVersion = pPCB->bProtocolVersion;
        pEapolPkt->PacketType = EAPOL_Start;
        HostToWireFormat16 ((WORD)0, (BYTE *)pEapolPkt->PacketBodyLength);

        // Send packet out on the port
        dwRetCode = ElWriteToPort (pPCB,
                                    (CHAR *)pEapolPkt,
                                    sizeof (EAPOL_PACKET));
        if (dwRetCode != NO_ERROR)
        {
            TRACE1 (EAPOL, "FSMConnecting: Error in writing Start Pkt to port %ld",
                    dwRetCode);
            break;
        }

        }

        TRACE1 (EAPOL, "Setting state CONNECTING for port %ws", pPCB->pwszFriendlyName);

        DbLogPCBEvent (DBLOG_CATEG_INFO, pPCB, EAPOL_STATE_TRANSITION, 
            EAPOLStates[((pPCB->State < EAPOLSTATE_LOGOFF) || (pPCB->State > EAPOLSTATE_AUTHENTICATED))?EAPOLSTATE_UNDEFINED:pPCB->State], 
            EAPOLStates[EAPOLSTATE_CONNECTING]);

        pPCB->State = EAPOLSTATE_CONNECTING;

        SET_EAPOL_START_TIMER(pPCB);

        // Reset UI interaction state
        pPCB->EapUIState = 0;

    } while (FALSE);

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

    TRACE1 (EAPOL, "FSMConnecting completed for port %ws", pPCB->pwszFriendlyName);
    return dwRetCode;
}


//
// FSMAcquired
//
// Description:
//      Function called when the port receives a EAP-Request/Identity packet.
//      EAP processing of the packet occurs and a EAP-Response/Identity may
//      be sent out by EAP if required.
//      
//
// Arguments:
//      pPCB - Pointer to the PCB for the port on which data is being
//      processed
//      pEapolPkt - Pointer to EAPOL packet that was received
//
// Return values:
//      NO_ERROR - success
//      non-zero - error
//

DWORD
FSMAcquired (
        IN  EAPOL_PCB       *pPCB,
        IN  EAPOL_PACKET    *pEapolPkt
        )
{
    DWORD       dwComputerNameLen = 0;
    GUID        DeviceGuid;
    DWORD       dwRetCode= NO_ERROR;

    TRACE1 (EAPOL, "FSMAcquired entered for port %ws", pPCB->pwszFriendlyName);

    do
    {
        // Flag that authentication has not completed in the EAP module
        // on the client-side.
        pPCB->fLocalEAPAuthSuccess = FALSE;
        pPCB->dwLocalEAPAuthResult = NO_ERROR;

        // Restart timer with authPeriod
        // Even if there is error in processing, the authtimer timeout
        // should happen
        RESTART_TIMER (pPCB->hTimer,
                pPCB->EapolConfig.dwauthPeriod,
                "PCB",
                &dwRetCode);
        if (dwRetCode != NO_ERROR)
        {
            TRACE1 (EAPOL, "FSMAcquired: Error in RESTART_TIMER %ld",
                    dwRetCode);
            break;
        }

        // Since an EAP Req-ID was received, reset EAPOL_Start count
        pPCB->ulStartCount = 0;

        // Flag that no EAPOL-Key transmit key was received
        pPCB->fTransmitKeyReceived = FALSE;

        // If current received EAP Id is the same the previous EAP Id
        // send the last EAPOL packet again

        if (((PPP_EAP_PACKET *)pEapolPkt->PacketBody)->Id == 
            pPCB->dwPreviousId)
        {
                
            TRACE0 (EAPOL, "FSMAcquired: Re-xmitting EAP_Packet to port");

            dwRetCode = ElWriteToPort (pPCB,
                            (CHAR *)pPCB->pbPreviousEAPOLPkt,
                            pPCB->dwSizeOfPreviousEAPOLPkt);
            if (dwRetCode != NO_ERROR)
            {
                TRACE1 (EAPOL, "FSMAcquired: Error in writing re-xmitted EAP_Packet to port %ld",
                        dwRetCode);
                break;
            }
        }
        else
        {
            // Indicate to EAP-Dll to cleanup any leftovers from earlier
            // authentication. This is to take care of cases where errors
            // occured in the earlier authentication and cleanup wasn't done
            if ((dwRetCode = ElEapEnd (pPCB)) != NO_ERROR)
            {
                TRACE1 (EAPOL, "FSMAcquired: Error in ElEapEnd = %ld",
                        dwRetCode);
                break;
            }

            // Process the EAP packet
            // ElEapWork will send out response if required
            if (( dwRetCode = ElEapWork (
                            pPCB,
                            (PPP_EAP_PACKET *)pEapolPkt->PacketBody
                            )) != NO_ERROR)
            {
                // Ignore error if UI is waiting for input
                if (dwRetCode != ERROR_IO_PENDING)
                {
                    TRACE1 (EAPOL, "FSMAcquired: Error in ElEapWork %ld",
                            dwRetCode);
                    break;
                }
                else
                {
                    dwRetCode = NO_ERROR;
                }
            }
        }

        TRACE1 (EAPOL, "Setting state ACQUIRED for port %ws", pPCB->pwszFriendlyName);

        SET_EAPOL_AUTH_TIMER(pPCB);

        DbLogPCBEvent (DBLOG_CATEG_INFO, pPCB, EAPOL_STATE_TRANSITION, 
            EAPOLStates[((pPCB->State < EAPOLSTATE_LOGOFF) || (pPCB->State > EAPOLSTATE_AUTHENTICATED))?EAPOLSTATE_UNDEFINED:pPCB->State], 
            EAPOLStates[EAPOLSTATE_ACQUIRED]);

        pPCB->State = EAPOLSTATE_ACQUIRED;
                
        // ElNetmanNotify (pPCB, EAPOL_NCS_CRED_REQUIRED, NULL);

    } while (FALSE);

    TRACE1 (EAPOL, "FSMAcquired completed for port %ws", pPCB->pwszFriendlyName);

    return dwRetCode;
}


//
// FSMAuthenticating
//
// Description:
//
// Function called when an non EAP-Request/Identity packet is received on the
// port. EAP processing of the data occurs.
//
// Arguments:
//      pPCB - Pointer to the PCB for the port on which data is being
//      processed
//      pEapolPkt - Pointer to EAPOL packet that was received
//
// Return values:
//      NO_ERROR - success
//      non-zero - error
//

DWORD
FSMAuthenticating (
        IN  EAPOL_PCB       *pPCB,
        IN  EAPOL_PACKET    *pEapolPkt
        )
{
    GUID            DeviceGuid;
    DWORD           dwRetCode = NO_ERROR;

    TRACE1 (EAPOL, "FSMAuthenticating entered for port %ws", pPCB->pwszFriendlyName);

    do
    {

        // Restart timer with authPeriod
        // Even if there is error in ElEapWork, the authtimer timeout
        // should happen
        RESTART_TIMER (pPCB->hTimer,
                pPCB->EapolConfig.dwauthPeriod,
                "PCB",
                &dwRetCode);
        if (dwRetCode != NO_ERROR)
        {
            TRACE1 (EAPOL, "FSMAuthenticating: Error in RESTART_TIMER %ld",
                    dwRetCode);
            break;
        }

        // If current received EAP Id is the same the previous EAP Id
        // send the last EAPOL packet again
	    // For EAPCODE_Success and EAPCODE_Failure, the value of id field
	    // will not be increment, Refer to EAP RFC 

        if ((((PPP_EAP_PACKET *)pEapolPkt->PacketBody)->Id 
                    == pPCB->dwPreviousId) &&
                (((PPP_EAP_PACKET *)pEapolPkt->PacketBody)->Code 
                    !=  EAPCODE_Success) &&
                (((PPP_EAP_PACKET *)pEapolPkt->PacketBody)->Code 
                    !=  EAPCODE_Failure))
        {

            TRACE0 (EAPOL, "FSMAuthenticating: Re-xmitting EAP_Packet to port");

            dwRetCode = ElWriteToPort (pPCB,
                            (CHAR *)pPCB->pbPreviousEAPOLPkt,
                            pPCB->dwSizeOfPreviousEAPOLPkt);
            if (dwRetCode != NO_ERROR)
            {
                TRACE1 (EAPOL, "FSMAuthenticating: Error in writing re-xmitted EAP_Packet to port = %ld",
                        dwRetCode);
                break;
            }
        }
        else
        {
            // Process the EAP packet
            // ElEapWork will send out response if required
            if (( dwRetCode = ElEapWork (
                            pPCB,
                            (PPP_EAP_PACKET *)pEapolPkt->PacketBody
                            )) != NO_ERROR)
            {
                TRACE1 (EAPOL, "FSMAuthenticating: Error in ElEapWork %ld",
                        dwRetCode);
                break;
            }
        }


        TRACE1 (EAPOL, "Setting state AUTHENTICATING for port %ws", pPCB->pwszFriendlyName);

        SET_EAPOL_AUTH_TIMER(pPCB);

        DbLogPCBEvent (DBLOG_CATEG_INFO, pPCB, EAPOL_STATE_TRANSITION, 
            EAPOLStates[((pPCB->State < EAPOLSTATE_LOGOFF) || (pPCB->State > EAPOLSTATE_AUTHENTICATED))?EAPOLSTATE_UNDEFINED:pPCB->State], 
            EAPOLStates[EAPOLSTATE_AUTHENTICATING]);

        pPCB->State = EAPOLSTATE_AUTHENTICATING;

        ElNetmanNotify (pPCB, EAPOL_NCS_AUTHENTICATING, NULL);

    } while (FALSE);

    TRACE1 (EAPOL, "FSMAuthenticating completed for port %ws", pPCB->pwszFriendlyName);

    return dwRetCode;
}


//
// FSMHeld
//
// Description:
//      Function called when a EAP-Failure packet is received in the
//      Authenticating state. State machine is held for heldPeriod before
//      re-authentication can occur.
//
// Arguments:
//      pPCB - Pointer to the PCB for the port on which data is being
//      processed
//
// Return values:
//      NO_ERROR - success
//      non-zero - error
//

DWORD
FSMHeld (
        IN  EAPOL_PCB       *pPCB,
        IN  EAPOL_PACKET    *pEapolPkt
        )
{
    DWORD       dwRetCode = NO_ERROR;

    TRACE1 (EAPOL, "FSMHeld entered for port %ws", pPCB->pwszFriendlyName);

    do 
    {
        TRACE1 (EAPOL, "FSMHeld: EAP authentication failed with error 0x%x",
                pPCB->dwLocalEAPAuthResult);

        // Delete current credentials only if there is actually an error
        // in the EAP module during processing.
        // Ignore EAP-Failures arising out of session time-outs on AP,
        // backend, etc.
        if (pPCB->dwLocalEAPAuthResult != NO_ERROR)
        {

        pPCB->dwAuthFailCount++;

        TRACE1 (EAPOL, "Restarting Held timer with time value = %ld",
                pPCB->EapolConfig.dwheldPeriod);

        TRACE1 (EAPOL, "FSMHeld: Setting state HELD for port %ws", 
                pPCB->pwszFriendlyName);

        // Free Identity buffer

        if (pPCB->pszIdentity != NULL)
        {
            FREE (pPCB->pszIdentity);
            pPCB->pszIdentity = NULL;
        }
    
        // Free Password buffer
    
        if (pPCB->PasswordBlob.pbData != NULL)
        {
            FREE (pPCB->PasswordBlob.pbData);
            pPCB->PasswordBlob.pbData = NULL;
            pPCB->PasswordBlob.cbData = 0;
        }

        // Free user-specific data in the PCB
    
        if (pPCB->pCustomAuthUserData != NULL)
        {
            FREE (pPCB->pCustomAuthUserData);
            pPCB->pCustomAuthUserData = NULL;
        }
    
        // Free connection data
    
        if (pPCB->pCustomAuthConnData != NULL)
        {
            FREE (pPCB->pCustomAuthConnData);
            pPCB->pCustomAuthConnData = NULL;
        }

        // Delete User Data stored in registry since it is invalid

        if (pPCB->pSSID != NULL)
        {
            if ((dwRetCode = ElDeleteEapUserInfo (
                                pPCB->hUserToken,
                                pPCB->pwszDeviceGUID,
                                pPCB->dwEapTypeToBeUsed,
                                pPCB->pSSID->SsidLength,
                                pPCB->pSSID->Ssid
                                )) != NO_ERROR)
            {
                TRACE1 (EAPOL, "FSMHeld: ElDeleteEapUserInfo failed with error %ld",
                        dwRetCode);
                dwRetCode = NO_ERROR;
            }
        }
        else
        {
            if ((dwRetCode = ElDeleteEapUserInfo (
                                pPCB->hUserToken,
                                pPCB->pwszDeviceGUID,
                                pPCB->dwEapTypeToBeUsed,
                                0,
                                NULL
                                )) != NO_ERROR)
            {
                TRACE1 (EAPOL, "FSMHeld: ElDeleteEapUserInfo failed with error %ld",
                        dwRetCode);
                dwRetCode = NO_ERROR;
            }
        }
    
        // Since there has been an error in credentials, start afresh
        // the authentication. Credentials may have changed e.g. certs 
        // may be renewed, MD5 credentials corrected etc.

        pPCB->fGotUserIdentity = FALSE;
    
        if (pPCB->hUserToken != NULL)
        {
            if (!CloseHandle (pPCB->hUserToken))
            {
                dwRetCode = GetLastError ();
                TRACE1 (EAPOL, "FSMHeld: CloseHandle failed with error %ld",
                    dwRetCode);
                dwRetCode = NO_ERROR;
            }
        }
        pPCB->hUserToken = NULL;

        DbLogPCBEvent (DBLOG_CATEG_ERR, pPCB, EAPOL_EAP_AUTHENTICATION_FAILED, pPCB->dwLocalEAPAuthResult);

        }
        else
        {
            if (pPCB->State == EAPOLSTATE_ACQUIRED)
            {
                DbLogPCBEvent (DBLOG_CATEG_ERR, pPCB, EAPOL_EAP_AUTHENTICATION_FAILED_ACQUIRED);
            }
            else
            {
                DbLogPCBEvent (DBLOG_CATEG_ERR, pPCB, EAPOL_EAP_AUTHENTICATION_FAILED_DEFAULT);
            }
        }

        DbLogPCBEvent (DBLOG_CATEG_INFO, pPCB, EAPOL_STATE_TRANSITION, 
            EAPOLStates[((pPCB->State < EAPOLSTATE_LOGOFF) || (pPCB->State > EAPOLSTATE_AUTHENTICATED))?EAPOLSTATE_UNDEFINED:pPCB->State], 
            EAPOLStates[EAPOLSTATE_HELD]);

        pPCB->State = EAPOLSTATE_HELD;

        TRACE1 (EAPOL, "FSMHeld: Port %ws set to HELD state",
                pPCB->pwszDeviceGUID);

        if (pPCB->dwLocalEAPAuthResult != NO_ERROR)
        {

        // If authfailed limit reached, go to Disconnected state
        if (pPCB->dwAuthFailCount >= pPCB->dwTotalMaxAuthFailCount)
        {
            TRACE2 (EAPOL, "FSMHeld: Fail count (%ld) > Max fail count (%ld)",
                    pPCB->dwAuthFailCount, pPCB->dwTotalMaxAuthFailCount);
            FSMDisconnected (pPCB, NULL);
            break;
        }

        }

        SET_EAPOL_HELD_TIMER(pPCB);

        // Restart timer with heldPeriod
        RESTART_TIMER (pPCB->hTimer,
                pPCB->EapolConfig.dwheldPeriod,
                "PCB",
                &dwRetCode);
        if (dwRetCode != NO_ERROR)
        {
            TRACE1 (EAPOL, "FSMHeld: Error in RESTART_TIMER %ld",
                    dwRetCode);

            break;
        }

    } while (FALSE);
    
    TRACE1 (EAPOL, "FSMHeld completed for port %ws", pPCB->pwszFriendlyName);

    return dwRetCode;
}


//
// FSMAuthenticated
//
// Description:
//
// Function called when a EAP-Success packet is received or MaxStart 
// EAPOL_Startpackets have been sent out, but no EAP-Request/Identity 
// packets were received. If EAP-Success packet is request, DHCP client 
// is restarted to get a new IP address.
//
// Arguments:
//      pPCB - Pointer to the PCB for the port on which data is being
//      processed
//      pEapolPkt - Pointer to EAPOL packet that was received
//
// Return values:
//      NO_ERROR - success
//      non-zero - error
//

DWORD
FSMAuthenticated (
        IN  EAPOL_PCB       *pPCB,
        IN  EAPOL_PACKET    *pEapolPkt
        )
{
    DHCP_PNP_CHANGE     DhcpPnpChange;
    WCHAR               *pwszGUIDBuffer = NULL;
    BOOLEAN             fReAuthenticatedWithSamePeer = FALSE;
    DWORD               dwRetCode = NO_ERROR;

    TRACE1 (EAPOL, "FSMAuthenticated entered for port %ws", 
            pPCB->pwszFriendlyName);

    do
    {
        // Shutdown earlier EAP session
        ElEapEnd (pPCB);

        // Call DHCP only if state machine went through authentication
        // If FSM is getting AUTHENTICATED by default, don't renew address
        // Also, if reauthentication is happening with same peer, namely in
        // wireless, don't renew address

#if 0
        if (pPCB->PhysicalMediumType == NdisPhysicalMediumWirelessLan)
        {
            if (!memcmp (pPCB->bDestMacAddr, pPCB->bPreviousDestMacAddr,
                        SIZE_MAC_ADDR))
            {
                fReAuthenticatedWithSamePeer = TRUE;
            }
            else
            {
                memcpy (pPCB->bPreviousDestMacAddr, pPCB->bDestMacAddr, 
                        SIZE_MAC_ADDR);
            }
        }
#endif

        if ((pPCB->ulStartCount < pPCB->EapolConfig.dwmaxStart) &&
                (!fReAuthenticatedWithSamePeer))
        {
            if ((pwszGUIDBuffer = MALLOC ((wcslen(pPCB->pwszDeviceGUID) + 1)*sizeof(WCHAR))) == NULL)
            {
                dwRetCode = ERROR_NOT_ENOUGH_MEMORY;
                break;
            }
            wcscpy (pwszGUIDBuffer, pPCB->pwszDeviceGUID);

            InterlockedIncrement (&g_lWorkerThreads);

            if (!QueueUserWorkItem (
                        (LPTHREAD_START_ROUTINE)ElIPPnPWorker,
                        (PVOID)pwszGUIDBuffer,
                        WT_EXECUTELONGFUNCTION
                        ))
            {
                InterlockedDecrement (&g_lWorkerThreads);
                FREE (pwszGUIDBuffer);
                dwRetCode = GetLastError();
                TRACE1 (PORT, "FSMAuthenticated: Critical error: QueueUserWorkItem failed with error %ld",
                        dwRetCode);
                // Ignore DHCP error, it's outside 802.1X logic
                dwRetCode = NO_ERROR;
            }
            else
            {
                TRACE0 (PORT, "FSMAuthenticated: Queued ElIPPnPWorker");
            }

        }
            
        TRACE1 (EAPOL, "Setting state AUTHENTICATED for port %ws", pPCB->pwszFriendlyName);

        DbLogPCBEvent (DBLOG_CATEG_INFO, pPCB, EAPOL_STATE_TRANSITION, 
            EAPOLStates[((pPCB->State < EAPOLSTATE_LOGOFF) || (pPCB->State > EAPOLSTATE_AUTHENTICATED))?EAPOLSTATE_UNDEFINED:pPCB->State], 
            EAPOLStates[EAPOLSTATE_AUTHENTICATED]);

        if (pPCB->fLocalEAPAuthSuccess)
        {
            DbLogPCBEvent (DBLOG_CATEG_INFO, pPCB, EAPOL_EAP_AUTHENTICATION_SUCCEEDED);
        }
        else
        {
            DbLogPCBEvent (DBLOG_CATEG_WARN, pPCB, EAPOL_EAP_AUTHENTICATION_DEFAULT);
        }

        pPCB->State = EAPOLSTATE_AUTHENTICATED;

        // In case of Wireless LAN ensure that there is EAPOL_Key packets 
        // received for transmit key
        if (pPCB->PhysicalMediumType == NdisPhysicalMediumWirelessLan)
        {
            if ((dwRetCode = ElSetEAPOLKeyReceivedTimer (pPCB)) != NO_ERROR)
            {
                TRACE1 (EAPOL, "FSMAuthenticated: ElSetEAPOLKeyReceivedTimer failed with error %ld",
                        dwRetCode);
                break;
            }
        }

    } while (FALSE);

    TRACE1 (EAPOL, "FSMAuthenticated completed for port %ws", pPCB->pwszFriendlyName);

    return dwRetCode;
}


//
// FSMKeyReceive
//
// Description:
//      Function called when an EAPOL-Key packet is received.
//      The WEP key is decrypted and plumbed down to the NIC driver.
//
// Arguments:
//      pPCB - Pointer to the PCB for the port on which data is being
//      processed
//      pEapolPkt - Pointer to EAPOL packet that was received
//
// Return values:
//      NO_ERROR - success
//      non-zero - error
//

DWORD
FSMKeyReceive (
        IN  EAPOL_PCB       *pPCB,
        IN  EAPOL_PACKET    *pEapolPkt
        )
{
    EAPOL_KEY_DESC      *pKeyDesc = NULL;
    DWORD               dwRetCode = NO_ERROR;

    TRACE1 (EAPOL, "FSMKeyReceive entered for port %ws", pPCB->pwszFriendlyName);

    do
    {
        pKeyDesc = (EAPOL_KEY_DESC *)pEapolPkt->PacketBody;

        switch (pKeyDesc->DescriptorType)
        {
            case EAPOL_KEY_DESC_RC4:
                if ((dwRetCode = ElKeyReceiveRC4 (pPCB,
                                    pEapolPkt)) != NO_ERROR)
                {
                    TRACE1 (EAPOL, "FSMKeyReceive: ElKeyReceiveRC4 failed with error %ld",
                            dwRetCode);
                }
                break;
#if 0
            case EAPOL_KEY_DESC_PER_STA:
                if ((dwRetCode = ElKeyReceivePerSTA (pPCB,
                                    pEapolPkt)) != NO_ERROR)
                {
                    TRACE1 (EAPOL, "FSMKeyReceive: ElKeyReceivePerSTA failed with error %ld",
                            dwRetCode);
                }
                break;
#endif
            default:
                dwRetCode = ERROR_INVALID_PARAMETER;
                TRACE1 (EAPOL, "FSMKeyReceive: Invalid DescriptorType (%ld)",
                        pKeyDesc->DescriptorType);
                break;
        }
    } 
    while (FALSE);

    if (dwRetCode != NO_ERROR)
    {
        DbLogPCBEvent (DBLOG_CATEG_ERR, pPCB, 
                EAPOL_ERROR_PROCESSING_EAPOL_KEY, dwRetCode);
    }

    TRACE1 (EAPOL, "FSMKeyReceive completed for port %ws", pPCB->pwszFriendlyName);

    return dwRetCode;
}


//
// ElKeyReceiveRC4
//
// Description:
//      Function called when an EAPOL-Key packet is received 
//      with RC4 DescriptorType
//
// Arguments:
//      pPCB - Pointer to the PCB for the port on which data is being
//      processed
//      pEapolPkt - Pointer to EAPOL packet that was received
//
// Return values:
//      NO_ERROR - success
//      non-zero - error
//

DWORD
ElKeyReceiveRC4 (
        IN  EAPOL_PCB       *pPCB,
        IN  EAPOL_PACKET    *pEapolPkt
        )
{
    EAPOL_KEY_DESC      *pKeyDesc = NULL;
    ULONGLONG           ullReplayCheck = 0; 
    BYTE                bReplayCheck[8];
    BYTE                *pbMD5EapolPkt = NULL;
    DWORD               dwMD5EapolPktLen = 0;
    DWORD               dwEapPktLen = 0;
    DWORD               dwIndex = 0;
    BYTE                bHMACMD5HashBuffer[MD5DIGESTLEN];
    RC4_KEYSTRUCT       rc4key;
    BYTE                bKeyBuffer[48];
    BYTE                *pbKeyToBePlumbed = NULL;
    DWORD               dwKeyLength = 0;
    NDIS_802_11_WEP     *pNdisWEPKey = NULL;
    BYTE                *pbMPPESendKey = NULL, *pbMPPERecvKey = NULL;
    DWORD               dwMPPESendKeyLength = 0, dwMPPERecvKeyLength = 0;

    DWORD               dwRetCode = NO_ERROR;

    TRACE1 (EAPOL, "ElKeyReceiveRC4 entered for port %ws", pPCB->pwszFriendlyName);

    do
    {
        if (WireToHostFormat16 (pEapolPkt->PacketBodyLength) < FIELD_OFFSET (EAPOL_KEY_DESC, Key))
        {
            TRACE0 (EAPOL, "ElKeyReceiveRC4: Invalid EAPOL-Key packet");
            dwRetCode = ERROR_INVALID_PACKET;
            break;
        }

        pKeyDesc = (EAPOL_KEY_DESC *)pEapolPkt->PacketBody;

        dwKeyLength = WireToHostFormat16 (pKeyDesc->KeyLength);

        if (WireToHostFormat16 (pEapolPkt->PacketBodyLength) > sizeof(EAPOL_KEY_DESC))
        {
            if (dwKeyLength != (WireToHostFormat16 (pEapolPkt->PacketBodyLength) - FIELD_OFFSET(EAPOL_KEY_DESC, Key)))

            {
                TRACE1 (EAPOL, "ElKeyReceiveRC4: Invalid Key Length in packet (%ld",
                        dwKeyLength);
                dwRetCode = ERROR_INVALID_PACKET;
                break;
            }
        }

        TRACE2 (EAPOL, "KeyLength = %ld, \n KeyIndex = %ld",
                dwKeyLength,
                pKeyDesc->KeyIndex
                );

        memcpy ((BYTE *)bReplayCheck, 
                (BYTE *)pKeyDesc->ReplayCounter, 
                8*sizeof(BYTE));

        ullReplayCheck = ((((ULONGLONG)(*((PBYTE)(bReplayCheck)+0))) << 56) +
                         (((ULONGLONG)(*((PBYTE)(bReplayCheck)+1))) << 48) +
                         (((ULONGLONG)(*((PBYTE)(bReplayCheck)+2))) << 40) +
                         (((ULONGLONG)(*((PBYTE)(bReplayCheck)+3))) << 32) +
                         (((ULONGLONG)(*((PBYTE)(bReplayCheck)+4))) << 24) +
                         (((ULONGLONG)(*((PBYTE)(bReplayCheck)+5))) << 16) +
                         (((ULONGLONG)(*((PBYTE)(bReplayCheck)+6))) << 8) +
                         (((ULONGLONG)(*((PBYTE)(bReplayCheck)+7)))));

        //
        // Check validity of Key message using the ReplayCounter field
        // Verify if it is in sync with the last ReplayCounter value 
        // received
        //
        
        // TRACE0 (EAPOL, "ElKeyReceiveRC4: Original replay counter in desc ======");
        // EAPOL_DUMPBA (pKeyDesc->ReplayCounter, 8);
        // TRACE0 (EAPOL, "ElKeyReceiveRC4: Converted incoming Replay counter ======= ");
        // EAPOL_DUMPBA ((BYTE *)&ullReplayCheck, 8);
        // TRACE0 (EAPOL, "ElKeyReceiveRC4: Last Replay counter ======= ");
        // EAPOL_DUMPBA ((BYTE *)&(pPCB->ullLastReplayCounter), 8);

        if (ullReplayCheck <= pPCB->ullLastReplayCounter)
        {
            TRACE0 (EAPOL, "ElKeyReceiveRC4: Replay counter is not in sync, something is wrong");
            DbLogPCBEvent (DBLOG_CATEG_ERR, pPCB, EAPOL_INVALID_EAPOL_KEY);
            break;
        }
        
        // If valid ReplayCounter, save it in the PCB for future check
        pPCB->ullLastReplayCounter = ullReplayCheck;

        //
        // Verify if the MD5 hash generated on the EAPOL packet,
        // with Signature nulled out, is the same as the signature
        // Use the MPPERecv key as the secret
        //

        dwEapPktLen = WireToHostFormat16 (pEapolPkt->PacketBodyLength);
        dwMD5EapolPktLen = sizeof (EAPOL_PACKET) - sizeof(pEapolPkt->EthernetType) - 1 + dwEapPktLen;
        if ((pbMD5EapolPkt = (BYTE *) MALLOC (dwMD5EapolPktLen)) == NULL)
        {
            TRACE0 (EAPOL, "ElKeyReceiveRC4: Error in MALLOC for pbMD5EapolPkt");
            dwRetCode = ERROR_NOT_ENOUGH_MEMORY;
            break;
        }

        memcpy ((BYTE *)pbMD5EapolPkt, (BYTE *)pEapolPkt+sizeof(pEapolPkt->EthernetType), dwMD5EapolPktLen);

        // Access the Master Send and Recv key stored locally
        if ((dwRetCode = ElSecureDecodePw (
                        &(pPCB->MasterSecretSend),
                        &(pbMPPESendKey),
                        &dwMPPESendKeyLength
                        )) != NO_ERROR)
        {
            TRACE1 (EAPOL, "ElKeyReceiveRC4: ElSecureDecodePw failed for MasterSecretSend with error %ld",
                                    dwRetCode);
            break;
        }
        if ((dwRetCode = ElSecureDecodePw (
                        &(pPCB->MasterSecretRecv),
                        &(pbMPPERecvKey),
                        &dwMPPERecvKeyLength
                        )) != NO_ERROR)
        {
            TRACE1 (EAPOL, "ElKeyReceiveRC4: ElSecureDecodePw failed for MasterSecretRecv with error %ld",
                                    dwRetCode);
            break;
        }

        //
        // Null out the signature in the key descriptor copy, to calculate
        // the hash on the supplicant side
        //

        ZeroMemory ((BYTE *)(pbMD5EapolPkt
                            - sizeof(pEapolPkt->EthernetType) +
                            sizeof(EAPOL_PACKET) - 1 + // pEapolPkt->Body
                            sizeof(EAPOL_KEY_DESC)- // End of EAPOL_KEY_DESC
                            MD5DIGESTLEN-1), // Signature field
                            MD5DIGESTLEN);

        (VOID) ElGetHMACMD5Digest (
            pbMD5EapolPkt,
            dwMD5EapolPktLen,
            pbMPPERecvKey,
            dwMPPERecvKeyLength,
            bHMACMD5HashBuffer
            );

        // TRACE0 (EAPOL, "ElKeyReceiveRC4: MD5 Hash body ==");
        // EAPOL_DUMPBA (pbMD5EapolPkt, dwMD5EapolPktLen);

        // TRACE0 (EAPOL, "ElKeyReceiveRC4: MD5 Hash secret ==");
        // EAPOL_DUMPBA (pbMPPERecvKey, dwMPPERecvKeyLength);

        // TRACE0 (EAPOL, "ElKeyReceiveRC4: MD5 Hash generated by Supplicant");
        // EAPOL_DUMPBA (bHMACMD5HashBuffer, MD5DIGESTLEN);

        // TRACE0 (EAPOL, "ElKeyReceiveRC4: Signature sent in EAPOL_KEY_DESC");
        // EAPOL_DUMPBA (pKeyDesc->KeySignature, MD5DIGESTLEN);

        //
        // Check if HMAC-MD5 hash in received packet is what is expected
        //
        if (memcmp (bHMACMD5HashBuffer, pKeyDesc->KeySignature, MD5DIGESTLEN) != 0)
        {
            TRACE0 (EAPOL, "ElKeyReceiveRC4: Signature in Key Desc does not match");
            DbLogPCBEvent (DBLOG_CATEG_ERR, pPCB, EAPOL_INVALID_EAPOL_KEY);
            break;
        }
            
        //
        // Decrypt the multicast WEP key if it has been provided
        //

        // Check if there is Key Material (5/16 bytes) at the end of 
        // the Key Descriptor

        if (WireToHostFormat16 (pEapolPkt->PacketBodyLength) > sizeof (EAPOL_KEY_DESC))

        {
            memcpy ((BYTE *)bKeyBuffer, (BYTE *)pKeyDesc->Key_IV, 16);
            memcpy ((BYTE *)&bKeyBuffer[16], (BYTE *)pbMPPESendKey, dwMPPESendKeyLength);

            rc4_key (&rc4key, 16 + dwMPPESendKeyLength, bKeyBuffer);
            rc4 (&rc4key, dwKeyLength, pKeyDesc->Key);

            // TRACE0 (EAPOL, " ========= The multicast key is ============= ");
            // EAPOL_DUMPBA (pKeyDesc->Key, dwKeyLength);

            // Use the unencrypted key in the Key Desc as the encryption key

            pbKeyToBePlumbed = pKeyDesc->Key;
            
        }
        else
        {
            if (dwKeyLength > dwMPPESendKeyLength)
            {
                TRACE1 (EAPOL, "ElKeyReceiveRC4: Invalid Key Length in packet (%ld",
                        dwKeyLength);
                dwRetCode = ERROR_INVALID_PACKET;
                break;
            }
            // Use the MPPESend key as the encryption key
            pbKeyToBePlumbed = (BYTE *)pbMPPESendKey;
        }

        if ((pNdisWEPKey = MALLOC ( sizeof(NDIS_802_11_WEP)-1+dwKeyLength )) 
                == NULL)
        {
            TRACE0 (EAPOL, "ElKeyReceiveRC4: MALLOC failed for pNdisWEPKey");
            dwRetCode = ERROR_NOT_ENOUGH_MEMORY;
            break;
        }

        pNdisWEPKey->Length = sizeof(NDIS_802_11_WEP) - 1 + dwKeyLength;
        memcpy ((BYTE *)pNdisWEPKey->KeyMaterial, (BYTE *)pbKeyToBePlumbed,
                dwKeyLength);
        pNdisWEPKey->KeyLength = dwKeyLength;


        // Create the long index out of the byte index got from AP
        // If MSB in byte is set, set MSB in ulong format

        if (pKeyDesc->KeyIndex & 0x80)
        {
            pNdisWEPKey->KeyIndex = 0x80000000;
        }
        else
        {
            pNdisWEPKey->KeyIndex = 0x00000000;
        }

        pNdisWEPKey->KeyIndex |= (pKeyDesc->KeyIndex & 0x03);

        // TRACE1 (ANY, "ElKeyReceiveRC4: Key Index is %x", pNdisWEPKey->KeyIndex);

        // Flag that transmit key was received
        if (pKeyDesc->KeyIndex & 0x80)
        {
            pPCB->fTransmitKeyReceived = TRUE;
        }

        // Use NDISUIO to plumb the key to the driver

        if ((dwRetCode = ElNdisuioSetOIDValue (
                                    pPCB->hPort,
                                    OID_802_11_ADD_WEP,
                                    (BYTE *)pNdisWEPKey,
                                    pNdisWEPKey->Length)) != NO_ERROR)
        {
            TRACE1 (PORT, "ElKeyReceiveRC4: ElNdisuioSetOIDValue failed with error %ld",
                    dwRetCode);
        }

    } 
    while (FALSE);

    if (dwRetCode != NO_ERROR)
    {
        DbLogPCBEvent (DBLOG_CATEG_ERR, pPCB, 
                EAPOL_ERROR_PROCESSING_EAPOL_KEY, dwRetCode);
    }

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

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

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

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

    TRACE1 (EAPOL, "ElKeyReceiveRC4 completed for port %ws", pPCB->pwszFriendlyName);

    return dwRetCode;
}

#if 0

//
// ElKeyReceivePerSTA
//
// Description:
//      Function called when an EAPOL-Key packet is received 
//      with PerSTA DescriptorType
//
// Arguments:
//      pPCB - Pointer to the PCB for the port on which data is being
//      processed
//      pEapolPkt - Pointer to EAPOL packet that was received
//
// Return values:
//      NO_ERROR - success
//      non-zero - error
//

DWORD
ElKeyReceivePerSTA (
        IN  EAPOL_PCB       *pPCB,
        IN  EAPOL_PACKET    *pEapolPkt
        )
{
    EAPOL_KEY_DESC      *pKeyDesc = NULL;
    ULONGLONG           ullReplayCheck = 0; 
    BYTE                bReplayCheck[8];
    BYTE                *pbMD5EapolPkt = NULL;
    DWORD               dwMD5EapolPktLen = 0;
    DWORD               dwEapPktLen = 0;
    DWORD               dwIndex = 0;
    BYTE                bHMACMD5HashBuffer[MD5DIGESTLEN];
    RC4_KEYSTRUCT       rc4key;
    BYTE                bKeyBuffer[48];
    BYTE                *pbKeyToBePlumbed = NULL;
    DWORD               dwRandomLength = 0;
    NDIS_802_11_WEP     *pNdisWEPKey = NULL;
    BYTE                *pbMasterSecretSend = NULL;
    DWORD               dwMasterSecretSendLength = 0;
    BYTE                *pbMasterSecretRecv = NULL;
    DWORD               dwMasterSecretRecvLength = 0;
    BYTE                *pbDynamicSendKey = NULL, *pbDynamicRecvKey = NULL;
    DWORD               dwDynamicKeyLength = 0;
    EAPOL_KEY_MATERIAL  *pEapolKeyMaterial = NULL;
    PBYTE               pbPaddedKeyMaterial = NULL;
    BOOLEAN             fIsUnicastKey = FALSE;
    SESSION_KEYS        OldSessionKeys = {0};
    SESSION_KEYS        NewSessionKeys = {0};
    DWORD               dwRetCode = NO_ERROR;

    TRACE1 (EAPOL, "ElKeyReceivePerSTA entered for port %ws", pPCB->pwszFriendlyName);

    do
    {
        pKeyDesc = (EAPOL_KEY_DESC *)pEapolPkt->PacketBody;

        dwDynamicKeyLength = WireToHostFormat16 (pKeyDesc->KeyLength);

        // TRACE2 (EAPOL, "ElKeyReceivePerSTA: KeyLength = %ld, \n KeyIndex = %0x",
                // dwDynamicKeyLength,
                // pKeyDesc->KeyIndex
                // );

        memcpy ((BYTE *)bReplayCheck, 
                (BYTE *)pKeyDesc->ReplayCounter, 
                8*sizeof(BYTE));

        ullReplayCheck = ((((ULONGLONG)(*((PBYTE)(bReplayCheck)+0))) << 56) +
                         (((ULONGLONG)(*((PBYTE)(bReplayCheck)+1))) << 48) +
                         (((ULONGLONG)(*((PBYTE)(bReplayCheck)+2))) << 40) +
                         (((ULONGLONG)(*((PBYTE)(bReplayCheck)+3))) << 32) +
                         (((ULONGLONG)(*((PBYTE)(bReplayCheck)+4))) << 24) +
                         (((ULONGLONG)(*((PBYTE)(bReplayCheck)+5))) << 16) +
                         (((ULONGLONG)(*((PBYTE)(bReplayCheck)+6))) << 8) +
                         (((ULONGLONG)(*((PBYTE)(bReplayCheck)+7)))));

        // Check validity of Key message using the ReplayCounter field
        // Verify if it is in sync with the last ReplayCounter value 
        // received
        
        // TRACE0 (EAPOL, "Original replay counter in desc ======");
        // EAPOL_DUMPBA (pKeyDesc->ReplayCounter, 8);
        // TRACE0 (EAPOL, "Converted incoming Replay counter ======= ");
        // EAPOL_DUMPBA ((BYTE *)&ullReplayCheck, 8);
        // TRACE0 (EAPOL, "Last Replay counter ======= ");
        // EAPOL_DUMPBA ((BYTE *)&(pPCB->ullLastReplayCounter), 8);

        if (ullReplayCheck <= pPCB->ullLastReplayCounter)
        {
            TRACE0 (EAPOL, "ElKeyReceivePerSTA: Replay counter is not in sync, something is wrong");
            DbLogPCBEvent (DBLOG_CATEG_ERR, pPCB, EAPOL_INVALID_EAPOL_KEY);
            break;
        }
        
        // If valid ReplayCounter, save it in the PCB for future check
        pPCB->ullLastReplayCounter = ullReplayCheck;

        // Verify if the MD5 hash generated on the EAPOL packet,
        // with Signature nulled out, is the same as the signature
        // Use the MPPERecv key as the secret

        dwEapPktLen = WireToHostFormat16 (pEapolPkt->PacketBodyLength);
        dwMD5EapolPktLen = sizeof (EAPOL_PACKET) - sizeof(pEapolPkt->EthernetType) - 1 + dwEapPktLen;
        if ((pbMD5EapolPkt = (BYTE *) MALLOC (dwMD5EapolPktLen)) == NULL)
        {
            TRACE0 (EAPOL, "ElKeyReceivePerSTA: Error in MALLOC for pbMD5EapolPkt");
            dwRetCode = ERROR_NOT_ENOUGH_MEMORY;
            break;
        }

        memcpy ((BYTE *)pbMD5EapolPkt, (BYTE *)pEapolPkt+sizeof(pEapolPkt->EthernetType), dwMD5EapolPktLen);

        // Query Master Secrets
        if (dwRetCode = ElQueryMasterKeys (
                    pPCB,
                    &OldSessionKeys
                ) != NO_ERROR)
        {
            TRACE1 (EAPOL, "ElKeyReceivePerSTA: ElQueryMasterKeys failed with error %ld",
                    dwRetCode);
            break;
        }
        pbMasterSecretSend = OldSessionKeys.bSendKey;
        pbMasterSecretRecv = OldSessionKeys.bReceiveKey;
        dwMasterSecretSendLength = OldSessionKeys.dwKeyLength;
        dwMasterSecretRecvLength = OldSessionKeys.dwKeyLength;

        // Null out the signature in the key descriptor copy, to calculate
        // the hash on the supplicant side
        ZeroMemory ((BYTE *)(pbMD5EapolPkt
                            - sizeof(pEapolPkt->EthernetType) +
                            sizeof(EAPOL_PACKET) - 1 + // pEapolPkt->Body
                            sizeof(EAPOL_KEY_DESC)- // End of EAPOL_KEY_DESC
                            MD5DIGESTLEN-1), // Signature field
                            MD5DIGESTLEN);

        (VOID) ElGetHMACMD5Digest (
            pbMD5EapolPkt,
            dwMD5EapolPktLen,
            pbMasterSecretRecv,
            dwMasterSecretRecvLength,
            bHMACMD5HashBuffer
            );

        // TRACE0 (EAPOL, "ElKeyReceivePerSTA: MD5 Hash body ==");
        // EAPOL_DUMPBA (pbMD5EapolPkt, dwMD5EapolPktLen);

        // TRACE0 (EAPOL, "ElKeyReceivePerSTA: MD5 Hash secret ==");
        // EAPOL_DUMPBA (pbMasterSecretRecv, dwMasterSecretRecvLength);

        // TRACE0 (EAPOL, "ElKeyReceivePerSTA: MD5 Hash generated by Supplicant");
        // EAPOL_DUMPBA (bHMACMD5HashBuffer, MD5DIGESTLEN);

        // TRACE0 (EAPOL, "ElKeyReceivePerSTA: Signature sent in EAPOL_KEY_DESC");
        // EAPOL_DUMPBA (pKeyDesc->KeySignature, MD5DIGESTLEN);

        // Check if HMAC-MD5 hash in received packet is what is expected
        if (memcmp (bHMACMD5HashBuffer, pKeyDesc->KeySignature, MD5DIGESTLEN) != 0)
        {
            TRACE0 (EAPOL, "ElKeyReceivePerSTA: Signature in Key Descriptor does not match");
            DbLogPCBEvent (DBLOG_CATEG_ERR, pPCB, EAPOL_INVALID_EAPOL_KEY);
            break;
        }

        if (pKeyDesc->KeyIndex & 0x80)
        {
            fIsUnicastKey = TRUE;
        }
            
        // Decrypt the random value if it has been provided
        if (WireToHostFormat16 (pEapolPkt->PacketBodyLength) > sizeof (EAPOL_KEY_DESC))
        {
            DWORD   dwKeyMaterialLength = 0;
            dwKeyMaterialLength = WireToHostFormat16 (pEapolPkt->PacketBodyLength) - FIELD_OFFSET(EAPOL_KEY_DESC, Key);

            // TRACE1 (EAPOL, "ElKeyReceivePerSTA: KeyMaterialLength = %ld",
                    // dwKeyMaterialLength);
            memcpy ((BYTE *)bKeyBuffer, (BYTE *)pKeyDesc->Key_IV, KEY_IV_LENGTH);
            memcpy ((BYTE *)&bKeyBuffer[KEY_IV_LENGTH], (BYTE *)pbMasterSecretSend, 
                    dwMasterSecretSendLength);

            pEapolKeyMaterial = (PEAPOL_KEY_MATERIAL)pKeyDesc->Key;
            dwRandomLength = WireToHostFormat16 (pEapolKeyMaterial->KeyMaterialLength);
            if ((pbPaddedKeyMaterial = (PBYTE)MALLOC (RC4_PAD_LENGTH + dwKeyMaterialLength)) == NULL)
            {
                dwRetCode = ERROR_NOT_ENOUGH_MEMORY;
                break;
            }
            memcpy (pbPaddedKeyMaterial+RC4_PAD_LENGTH, pEapolKeyMaterial->KeyMaterial, dwKeyMaterialLength);

            rc4_key (&rc4key, KEY_IV_LENGTH+dwMasterSecretSendLength, bKeyBuffer);
            rc4 (&rc4key, dwKeyMaterialLength+RC4_PAD_LENGTH, pbPaddedKeyMaterial);
            // Ignore leading padded RC4_PAD_LENGTH bytes
            memcpy (pEapolKeyMaterial->KeyMaterial, pbPaddedKeyMaterial+RC4_PAD_LENGTH, dwKeyMaterialLength);

            // TRACE1 (EAPOL, "ElKeyReceivePerSTA: Randomlength = %ld",
                     // dwRandomLength);
            // TRACE0 (EAPOL, "ElKeyReceivePerSTA: ========= The random material is ============= ");
            // EAPOL_DUMPBA (pEapolKeyMaterial->KeyMaterial, dwRandomLength);
        }
        else
        {
            // No random material sent
            TRACE0 (EAPOL, "ElKeyReceivePerSTA: Did not find random material: Exiting");
            dwRetCode = ERROR_INVALID_PARAMETER;
            break;
        }

        if (fIsUnicastKey)
        {
            
        TRACE0 (EAPOL, "ElKeyReceivePerSTA: Received Per-STA Unicast key material Random");

        // Generate dynamic keys 
        if (dwRetCode = GenerateDynamicKeys (
                    pbMasterSecretSend,
                    dwMasterSecretSendLength,
                    pEapolKeyMaterial->KeyMaterial,
                    dwRandomLength,
                    dwDynamicKeyLength,
                    &NewSessionKeys
                    ) != NO_ERROR)
        {
            TRACE1 (EAPOL, "ElKeyReceivePerSTA: ElGenerateDynamicKeys failed with error %ld",
                    dwRetCode);
            break;
        }
                
        pbDynamicSendKey = NewSessionKeys.bSendKey;
        pbDynamicRecvKey = NewSessionKeys.bReceiveKey;

        // TRACE0 (EAPOL, "ElKeyReceivePerSTA: Derived Send Key");
        // EAPOL_DUMPBA (pbDynamicSendKey, dwDynamicKeyLength);
        // TRACE0 (EAPOL, "ElKeyReceivePerSTA: Derived Recv Key");
        // EAPOL_DUMPBA (pbDynamicRecvKey, dwDynamicKeyLength);

        // Update Master Secrets
        if (dwRetCode = ElSetMasterKeys (
                    pPCB,
                    &NewSessionKeys
                ) != NO_ERROR)
        {
            // Cannot do much about this error than proceed
            TRACE1 (EAPOL, "ElKeyReceivePerSTA: ElSetMasterKeys failed with error %ld",
                    dwRetCode);
            dwRetCode = NO_ERROR;
        }

        pbKeyToBePlumbed = pbDynamicSendKey;

        }
        else
        {
            
        TRACE0 (EAPOL, "ElKeyReceivePerSTA: Received Per-STA BROADCAST key material");
        if (dwRandomLength != dwDynamicKeyLength)
        {
            TRACE2 (EAPOL, "ElKeyReceivePerSTA: KeyLength (%ld) != KeyMaterialLength (%ld), Inconsistent. Will consider only KeyMaterial length !", 
                    dwDynamicKeyLength, dwRandomLength);
        }

        dwDynamicKeyLength = dwRandomLength;
        pbKeyToBePlumbed = pEapolKeyMaterial->KeyMaterial;

        }

        if ((pNdisWEPKey = MALLOC ( sizeof(NDIS_802_11_WEP)-1+dwDynamicKeyLength )) 
                == NULL)
        {
            TRACE0 (EAPOL, "ElKeyReceivePerSTA: MALLOC failed for pNdisWEPKey");
            dwRetCode = ERROR_NOT_ENOUGH_MEMORY;
            break;
        }

        pNdisWEPKey->Length = sizeof(NDIS_802_11_WEP) - 1 + dwDynamicKeyLength;
        memcpy ((BYTE *)pNdisWEPKey->KeyMaterial, (BYTE *)pbKeyToBePlumbed,
                dwDynamicKeyLength);
        pNdisWEPKey->KeyLength = dwDynamicKeyLength;

        // Create the long index out of the byte index got from AP
        // If MSB in byte is set, set MSB in ulong format

        if (pKeyDesc->KeyIndex & 0x80)
        {
            pNdisWEPKey->KeyIndex = 0x80000000;
        }
        else
        {
            pNdisWEPKey->KeyIndex = 0x00000000;
        }

        pNdisWEPKey->KeyIndex |= (pKeyDesc->KeyIndex & 0x03);

        // Use NDISUIO to plumb the key to the driver
        if ((dwRetCode = ElNdisuioSetOIDValue (
                                    pPCB->hPort,
                                    OID_802_11_ADD_WEP,
                                    (BYTE *)pNdisWEPKey,
                                    pNdisWEPKey->Length)) != NO_ERROR)
        {
            TRACE1 (PORT, "ElKeyReceivePerSTA: ElNdisuioSetOIDValue failed with error %ld",
                    dwRetCode);
        }
    } 
    while (FALSE);

    if (dwRetCode != NO_ERROR)
    {
        DbLogPCBEvent (DBLOG_CATEG_ERR, pPCB, 
                EAPOL_ERROR_PROCESSING_EAPOL_KEY, dwRetCode);
    }

    if (pbMD5EapolPkt != NULL)
    {
        FREE (pbMD5EapolPkt);
        pbMD5EapolPkt = NULL;
    }
    if (pNdisWEPKey != NULL)
    {
        FREE (pNdisWEPKey);
        pNdisWEPKey = NULL;
    }
    if (pbPaddedKeyMaterial != NULL)
    {
        FREE (pbPaddedKeyMaterial);
    }

    TRACE1 (EAPOL, "ElKeyReceivePerSTA completed for port %ws", pPCB->pwszFriendlyName);

    return dwRetCode;
}

#endif


//
// ElTimeoutCallbackRoutine
//
// Description:
//
// Function called when any timer work item queued on the global timer 
// queue expires. Depending on the state in which the port is when the timer
// expires, the port moves to the next state.
//
// Arguments:
//      pvContext - Pointer to context. In this case, it is pointer to a PCB 
//      fTimerOfWaitFired - Unused
//
// Return values:
//

VOID 
ElTimeoutCallbackRoutine (
        IN  PVOID       pvContext,
        IN  BOOLEAN     fTimerOfWaitFired
        )
{
    EAPOL_PCB       *pPCB;

    TRACE0 (EAPOL, "ElTimeoutCallbackRoutine entered");
    
    do 
    {
        // Context should not be NULL
        if (pvContext == NULL)
        {
            TRACE0 (EAPOL, "ElTimeoutCallbackRoutine: pvContext is NULL. Invalid timeout callback");
            break;
        }

        // PCB is guaranteed to exist until all timers are fired
            
        // Verify if Port is still active
        pPCB = (EAPOL_PCB *)pvContext;
        ACQUIRE_WRITE_LOCK (&(pPCB->rwLock));

        if (!EAPOL_PORT_ACTIVE(pPCB))
        {
            // Port is not active
            RELEASE_WRITE_LOCK (&(pPCB->rwLock));
            TRACE1 (PORT, "ElTimeoutCallbackRoutine: Port %ws is inactive",
                    pPCB->pwszDeviceGUID);
            break;
        }

        DbLogPCBEvent (DBLOG_CATEG_INFO, pPCB, EAPOL_STATE_TIMEOUT, 
            EAPOLStates[((pPCB->State < EAPOLSTATE_LOGOFF) || (pPCB->State > EAPOLSTATE_AUTHENTICATED))?EAPOLSTATE_UNDEFINED:pPCB->State]);

        // Check the current state of the state machine 
        // We can do additional checks such as flagging which timer was fired
        // and in the timeout checking if the PCB state has remained the same
        // Else bail out
    
        switch (pPCB->State)
        {
            case EAPOLSTATE_CONNECTING:
                if (!EAPOL_START_TIMER_SET(pPCB))
                {
                    TRACE1 (EAPOL, "ElTimeoutCallbackRoutine: Wrong timeout %ld in Connecting state", CHECK_EAPOL_TIMER(pPCB));
                    break;
                }
                pPCB->dwTimerFlags &= ~EAPOL_START_TIMER;
                FSMConnecting(pPCB, NULL);
                break;
    
            case EAPOLSTATE_ACQUIRED:
                if (!EAPOL_AUTH_TIMER_SET(pPCB))
                {
                    TRACE1 (EAPOL, "ElTimeoutCallbackRoutine: Wrong timeout %ld in Acquired state", CHECK_EAPOL_TIMER(pPCB));
                    break;
                }
                pPCB->dwTimerFlags &= ~EAPOL_AUTH_TIMER;
                FSMConnecting(pPCB, NULL);
                break;
                
            case EAPOLSTATE_AUTHENTICATING:
                if (!EAPOL_AUTH_TIMER_SET(pPCB))
                {
                    TRACE1 (EAPOL, "ElTimeoutCallbackRoutine: Wrong timeout %ld in Authenticating state", CHECK_EAPOL_TIMER(pPCB));
                    break;
                }
                pPCB->dwTimerFlags &= ~EAPOL_AUTH_TIMER;
                FSMConnecting(pPCB, NULL);
                break;
                
            case EAPOLSTATE_AUTHENTICATED:
                if (!EAPOL_TRANSMIT_KEY_TIMER_SET(pPCB))
                {
                    TRACE1 (EAPOL, "ElTimeoutCallbackRoutine: Wrong timeout %ld in Authenticated state", CHECK_EAPOL_TIMER(pPCB));
                    break;
                }
                pPCB->dwTimerFlags &= ~EAPOL_TRANSMIT_KEY_TIMER;
                ElVerifyEAPOLKeyReceived(pPCB);
                break;
                
            case EAPOLSTATE_HELD:
                if (!EAPOL_HELD_TIMER_SET(pPCB))
                {
                    TRACE1 (EAPOL, "ElTimeoutCallbackRoutine: Wrong timeout %ld in Held state", CHECK_EAPOL_TIMER(pPCB));
                    break;
                }

                // Go through logoff, since new user will be tried
                // for next cycle
                // Debatable !
                if (!(pPCB->dwAuthFailCount % EAPOL_MAX_AUTH_FAIL_COUNT))
                {
                    // FSMLogoff (pPCB, NULL);
                }
                FSMConnecting(pPCB, NULL);
                break;

            case EAPOLSTATE_DISCONNECTED:
                TRACE0 (EAPOL, "ElTimeoutCallbackRoutine: No action in Disconnected state");
                break;
                
            case EAPOLSTATE_LOGOFF:
                TRACE0 (EAPOL, "ElTimeoutCallbackRoutine: No action in Logoff state");
                break;
                
            default:
                TRACE0 (EAPOL, "ElTimeoutCallbackRoutine: Critical Error. Invalid state after timer expires ");
                break;
        }
    
        RELEASE_WRITE_LOCK (&(pPCB->rwLock));

    } while (FALSE);
            
    TRACE0 (EAPOL, "ElTimeoutCallbackRoutine completed");

    return;
}


//
// ElEapWork
//
// Description:
//
// Function called when an EAPOL packet of type EAP_Packet is received 
// The EAP packet is passed to the EAP module for processing.
// Depending on the result of the processing, a EAP Response packet
// is sent or the incoming packet is ignored.
//
// Input arguments:
//  pPCB - Pointer to PCB for the port on which data is being processed
//  pRecvPkt - Pointer to EAP packet in the data received from the remote end
//              
// Return values:
//      NO_ERROR - success
//      non-zero - error
//

//
// ISSUE: Rewrite with do {} while(FALSE)
//

DWORD
ElEapWork (
    IN EAPOL_PCB        *pPCB,
    IN PPP_EAP_PACKET   *pRecvPkt
    )
{
    DWORD           dwLength = 0;
    ELEAP_RESULT    EapResult;
    PPP_EAP_PACKET  *pSendPkt;
    EAPOL_PACKET    *pEapolPkt;
    GUID            DeviceGuid;
    DWORD           dwReceivedId = 0;
    DWORD           cbData = 0;
    BYTE            *pbAuthData = NULL;
    DWORD           dwRetCode = NO_ERROR;

    //
    // If the protocol has not been started yet, call ElEapBegin
    //

    if (!(pPCB->fEapInitialized))
    {
        if ((dwRetCode = ElEapBegin (pPCB)) != NO_ERROR)
        {
            TRACE1 (EAPOL, "ElEapWork: Error in ElEapBegin = %ld", dwRetCode);
            return dwRetCode;
        }
    }

    ZeroMemory(&EapResult, sizeof(EapResult));

    // Create buffer for EAPOL + EAP and pass pointer to EAP header

    pEapolPkt = (EAPOL_PACKET *) MALLOC (MAX_EAPOL_BUFFER_SIZE); 

    TRACE1 (EAPOL, "ElEapWork: EapolPkt created at %p", pEapolPkt);

    if (pEapolPkt == NULL)
    {
        TRACE0 (EAPOL, "ElEapWork: Error allocating EAP buffer");
        dwRetCode = ERROR_NOT_ENOUGH_MEMORY;
        return dwRetCode;
    }

    // Point to EAP header
    pSendPkt = (PPP_EAP_PACKET *)((PBYTE)pEapolPkt + sizeof (EAPOL_PACKET) - 1);

    if (pRecvPkt != NULL)
    {
        dwReceivedId = pRecvPkt->Id;
    }

    dwRetCode = ElEapMakeMessage (pPCB,
                                pRecvPkt,
                                pSendPkt,
                                MAX_EAPOL_BUFFER_SIZE 
                                - sizeof(EAPOL_PACKET) - 1,
                                &EapResult
                                );

    // Notification message for the user

    if (NULL != EapResult.pszReplyMessage)
    {
        // Free earlier notication with the PCB
        if (pPCB->pwszEapReplyMessage != NULL)
        {
            FREE (pPCB->pwszEapReplyMessage);
            pPCB->pwszEapReplyMessage = NULL;
        }

        pPCB->pwszEapReplyMessage = 
            (WCHAR *)MALLOC ((strlen(EapResult.pszReplyMessage)+1) * sizeof(WCHAR));

        if (pPCB->pwszEapReplyMessage == NULL)
        {
            dwRetCode = ERROR_NOT_ENOUGH_MEMORY;
            TRACE0 (EAPOL, "ElEapWork: MALLOC failed for pwszEapReplyMessage");
            FREE (EapResult.pszReplyMessage);
            FREE (pEapolPkt);
            pEapolPkt = NULL;
            return dwRetCode;
        }

        if (0 == MultiByteToWideChar (
                    CP_ACP,
                    0,
                    EapResult.pszReplyMessage,
                    -1,
                    pPCB->pwszEapReplyMessage,
                    strlen(EapResult.pszReplyMessage)+1))
        {
            dwRetCode = GetLastError();
            TRACE2 (EAPOL,"ElEapWork: MultiByteToWideChar(%s) failed for pwszEapReplyMessage with error (%ld)",
                                        EapResult.pszReplyMessage,
                                        dwRetCode);
            FREE (EapResult.pszReplyMessage);
            FREE (pEapolPkt);
            pEapolPkt = NULL;
            return dwRetCode;
        }



        ElNetmanNotify (pPCB, EAPOL_NCS_NOTIFICATION, NULL);

        TRACE1 (EAPOL, "ElEapWork: Notified user of EAP data = %ws",
              pPCB->pwszEapReplyMessage);

        FREE (EapResult.pszReplyMessage);
    }

    if (dwRetCode != NO_ERROR)
    {
        switch (dwRetCode)
        {
            case ERROR_PPP_INVALID_PACKET:

                TRACE0 (EAPOL, "ElEapWork: Silently discarding invalid auth packet");
                break;
    
            default:

                TRACE1 (EAPOL, "ElEapWork: ElEapMakeMessage returned error %ld",
                                                                dwRetCode);

                // NotifyCallerOfFailure (pPCB, dwRetCode);

                break;
        }

        // Free up memory reserved for packet
        FREE (pEapolPkt);
        pEapolPkt = NULL;

        return dwRetCode;
    }

    //
    // Check to see if we have to save any user data
    //

    if (EapResult.fSaveUserData) 
    {
        // Save to Registry

        if ((dwRetCode = ElSetEapUserInfo (
                        pPCB->hUserToken,
                        pPCB->pwszDeviceGUID,
                        pPCB->dwEapTypeToBeUsed,
                        (pPCB->pSSID)?pPCB->pSSID->SsidLength:0,
                        (pPCB->pSSID)?pPCB->pSSID->Ssid:NULL,
                        EapResult.pUserData,
                        EapResult.dwSizeOfUserData)) != NO_ERROR)
        {
            TRACE1 (EAPOL, "ElEapWork: ElSetEapUserInfo failed with error = %d",
                    dwRetCode);
            if (pEapolPkt != NULL)
            {
                FREE (pEapolPkt);
                pEapolPkt = NULL;
            }
            return dwRetCode;
        }

        // Save to PCB context

        if (pPCB->pCustomAuthUserData != NULL)
        {
            FREE (pPCB->pCustomAuthUserData);
            pPCB->pCustomAuthUserData = NULL;
        }

        pPCB->pCustomAuthUserData = MALLOC (EapResult.dwSizeOfUserData + sizeof (DWORD));
        if (pPCB->pCustomAuthUserData == NULL)
        {
            TRACE1 (EAPOL, "ElEapWork: Error in allocating memory for pCustomAuthUserData = %ld",
                    dwRetCode);
            dwRetCode = ERROR_NOT_ENOUGH_MEMORY;
            return dwRetCode;
        }

        pPCB->pCustomAuthUserData->dwSizeOfCustomAuthData = EapResult.dwSizeOfUserData;

        if ((EapResult.dwSizeOfUserData != 0) && (EapResult.pUserData != NULL))
        {
            memcpy ((BYTE *)pPCB->pCustomAuthUserData->pbCustomAuthData, 
                (BYTE *)EapResult.pUserData,
                EapResult.dwSizeOfUserData);
        }

        TRACE0 (EAPOL, "ElEapWork: Saved EAP data for user");
    }

    //
    // Check to see if we have to save any connection data
    //

    pbAuthData = EapResult.SetCustomAuthData.pConnectionData;
    cbData = EapResult.SetCustomAuthData.dwSizeOfConnectionData;

    if ((EapResult.fSaveConnectionData ) &&
         ( 0 != cbData ) )
    {
        // Save to registry
           
        if ((dwRetCode = ElSetCustomAuthData (
                        pPCB->pwszDeviceGUID,
                        pPCB->dwEapTypeToBeUsed,
                        (pPCB->pSSID)?pPCB->pSSID->SsidLength:0,
                        (pPCB->pSSID)?pPCB->pSSID->Ssid:NULL,
                        pbAuthData,
                        &cbData
                        )) != NO_ERROR)
        {
            TRACE1 ( EAPOL, "ElEapWork: ElSetCustomAuthData failed with error = %d",
                    dwRetCode);
            FREE (pEapolPkt);
            pEapolPkt = NULL;
            return dwRetCode;
        }

        // Save to PCB context

        if (pPCB->pCustomAuthConnData != NULL)
        {
            FREE (pPCB->pCustomAuthConnData);
            pPCB->pCustomAuthConnData = NULL;
        }

        pPCB->pCustomAuthConnData = MALLOC (cbData + sizeof (DWORD));
        if (pPCB->pCustomAuthConnData == NULL)
        {
            dwRetCode = ERROR_NOT_ENOUGH_MEMORY;
            TRACE1 (EAPOL, "ElEapWork: Error in allocating memory for pCustomAuthConnData = %ld",
                    dwRetCode);
            return dwRetCode;
        }

        pPCB->pCustomAuthConnData->dwSizeOfCustomAuthData = cbData;

        if ((cbData != 0) && (pbAuthData != NULL))
        {
            memcpy ((BYTE *)pPCB->pCustomAuthConnData->pbCustomAuthData, 
                (BYTE *)pbAuthData, 
                cbData);
        }

        TRACE0 (EAPOL, "ElEapWork: Saved EAP data for connection");
    }

    switch( EapResult.Action )
    {

        case ELEAP_Send:
        case ELEAP_SendAndDone:

            // Send out EAPOL packet

            memcpy ((BYTE *)pEapolPkt->EthernetType, 
                    (BYTE *)pPCB->bEtherType, 
                    SIZE_ETHERNET_TYPE);
            pEapolPkt->ProtocolVersion = pPCB->bProtocolVersion;
            pEapolPkt->PacketType = EAP_Packet;

            // The EAP packet length is in the packet returned back by 
            // the Dll MakeMessage
            // In case of Notification and Identity Response, it is in
            // EapResult.wSizeOfEapPkt

            if (EapResult.wSizeOfEapPkt == 0)
            {
                EapResult.wSizeOfEapPkt = 
                    WireToHostFormat16 (pSendPkt->Length);
            }
            HostToWireFormat16 ((WORD) EapResult.wSizeOfEapPkt,
                    (BYTE *)pEapolPkt->PacketBodyLength);


            // Make a copy of the EAPOL packet in the PCB
            // Will be used during retransmission

            if (pPCB->pbPreviousEAPOLPkt != NULL)
            {
                FREE (pPCB->pbPreviousEAPOLPkt);
                pPCB->pbPreviousEAPOLPkt = NULL;
            }
            pPCB->pbPreviousEAPOLPkt = 
                MALLOC (sizeof (EAPOL_PACKET)+EapResult.wSizeOfEapPkt-1);

            if (pPCB->pbPreviousEAPOLPkt == NULL)
            {
                dwRetCode = ERROR_NOT_ENOUGH_MEMORY;
                TRACE0 (EAPOL, "ElEapWork: MALLOC failed for pbPreviousEAPOLPkt");
                if (pEapolPkt != NULL)
                {
                    FREE (pEapolPkt);
                    pEapolPkt = NULL;
                }
                return dwRetCode;
            }

            memcpy (pPCB->pbPreviousEAPOLPkt, pEapolPkt, 
                    sizeof (EAPOL_PACKET)+EapResult.wSizeOfEapPkt-1);

            pPCB->dwSizeOfPreviousEAPOLPkt = 
                sizeof (EAPOL_PACKET)+EapResult.wSizeOfEapPkt-1;

            pPCB->dwPreviousId = dwReceivedId;


            // Send packet out on the port
            dwRetCode = ElWriteToPort (pPCB,
                            (CHAR *)pEapolPkt,
                            sizeof (EAPOL_PACKET)+EapResult.wSizeOfEapPkt-1);
            if (dwRetCode != NO_ERROR)
            {
                TRACE1 (EAPOL, "ElEapWork: Error in writing EAP_Packet to port %ld",
                        dwRetCode);
                if (pEapolPkt != NULL)
                {
                    FREE (pEapolPkt);
                    pEapolPkt = NULL;
                }
                return dwRetCode;
            }

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

            // More processing to be done?
            // Supplicant side should not ever receive ELEAP_SendAndDone
            // result code

            if (EapResult.Action != ELEAP_SendAndDone)
            {
                break;
            }
            else
            {
                TRACE0 (EAPOL, "ElEapWork: ELEAP_SendAndDone wrong result received");
            }
    
        case ELEAP_Done:
    
            // Retrieve MPPE keys from the attributes information
            // returned by EAP-TLS

            switch (EapResult.dwError)
            {
            case NO_ERROR:
    
                TRACE0 (EAPOL, "ElEapWork: Authentication was successful");

                pPCB->fLocalEAPAuthSuccess = TRUE;

                //
                // If authentication was successful
                //
    
                dwRetCode = ElExtractMPPESendRecvKeys (
                                            pPCB, 
                                            EapResult.pUserAttributes,
                                            (BYTE*)&(EapResult.abChallenge),
                                            (BYTE*)&(EapResult.abResponse));
    
                if (dwRetCode != NO_ERROR)
                {
                    FREE (pEapolPkt);
                    //NotifyCallerOfFailure (pPcb, dwRetCode);

                    return dwRetCode;
                }
    
                // ISSUE:
                // Do we want to retain UserAttributes
                // pPCB->pAuthProtocolAttributes = EapResult.pUserAttributes;
    
                break;
    

            default:
                if (pEapolPkt != NULL)
                {
                    FREE (pEapolPkt);
                    pEapolPkt = NULL;
                }
                TRACE0 (EAPOL, "ElEapWork: Authentication FAILED");
                
                pPCB->dwLocalEAPAuthResult = EapResult.dwError;

                break;
            }

            // Free memory allocated for the packet, since no response
            // is going to be sent out
            if (pEapolPkt != NULL)
            {
                FREE (pEapolPkt);
                pEapolPkt = NULL;
            }

            break;
    
        case ELEAP_NoAction:
            // Free memory allocated for the packet, since nothing
            // is being done with it
            if (pEapolPkt != NULL)
            {
                FREE (pEapolPkt);
                pEapolPkt = NULL;
            }

            break;
    
        default:
    
            break;
    }
    
    if (pEapolPkt != NULL)
    {
        FREE (pEapolPkt);
        pEapolPkt = NULL;
    }

    //
    // Check to see if we have to bring up the InteractiveUI for EAP
    // i.e. Server cert confirmation etc.
    //
    
    if (EapResult.fInvokeEapUI)
    {
        ElInvokeInteractiveUI (pPCB, &(EapResult.InvokeEapUIData));
    }

    return dwRetCode;
}


//
//
// ElExtractMPPESendRecvKeys
//
// Description:
//      Function called if authentication was successful. The MPPE Send &
//      Recv keys are extracted from the RAS_AUTH_ATTRIBUTE passed from
//      the EAP DLL and stored in the PCB. The keys are used to decrypt
//      the multicast WEP key and also are used for media-based encrypting.
//
// Return values
// 
//      NO_ERROR - Success
//      Non-zero - Failure
//

DWORD
ElExtractMPPESendRecvKeys (
    IN  EAPOL_PCB               *pPCB, 
    IN  RAS_AUTH_ATTRIBUTE *    pUserAttributes,
    IN  BYTE *                  pChallenge,
    IN  BYTE *                  pResponse
)
{
    RAS_AUTH_ATTRIBUTE *    pAttribute;
    RAS_AUTH_ATTRIBUTE *    pAttributeSendKey;
    RAS_AUTH_ATTRIBUTE *    pAttributeRecvKey;
    DWORD                   dwRetCode = NO_ERROR;
    DWORD                   dwEncryptionPolicy  = 0;
    DWORD                   dwEncryptionTypes   = 0;

    do
    {
        pAttribute = ElAuthAttributeGetVendorSpecific (
                                311, 12, pUserAttributes);


        pAttributeSendKey = ElAuthAttributeGetVendorSpecific ( 311, 16,
                                pUserAttributes);
        pAttributeRecvKey = ElAuthAttributeGetVendorSpecific ( 311, 17,
                                pUserAttributes);

        if ((pAttributeSendKey != NULL) 
            && (pAttributeRecvKey != NULL))
        {
            // Set the MS-MPPE-Send-Key and MS-MPPE-Recv-Key with 
            // the ethernet driver

            ULONG ulSendKeyLength = 0;
            ULONG ulRecvKeyLength = 0;

            // Based on PPP code
            ulSendKeyLength = *(((BYTE*)(pAttributeSendKey->Value))+8);
            ulRecvKeyLength = *(((BYTE*)(pAttributeRecvKey->Value))+8);
            // TRACE0 (EAPOL, "Send key = ");
            // EAPOL_DUMPBA (((BYTE*)(pAttributeSendKey->Value))+9,
                    // ulSendKeyLength);

            // TRACE0 (EAPOL, "Recv key = ");
            // EAPOL_DUMPBA (((BYTE*)(pAttributeRecvKey->Value))+9,
                    // ulRecvKeyLength);

            //
            // Copy MPPE Send and Receive Keys into the PCB for later usage
            // These keys will be used to decrypt keys sent by NAS (if any).
            // Save the keys as the MasterSecret for dynamic rekeying (if any).
            //

            if (ulSendKeyLength != 0)
            {
                if (pPCB->MasterSecretSend.cbData != 0)
                {
                    FREE (pPCB->MasterSecretSend.pbData);
                    pPCB->MasterSecretSend.cbData = 0;
                    pPCB->MasterSecretSend.pbData = NULL;
                }

                if ((dwRetCode = ElSecureEncodePw (
                                ((BYTE*)(pAttributeSendKey->Value))+9,
                                ulSendKeyLength,
                                &(pPCB->MasterSecretSend)
                            )) != NO_ERROR)
                {
                    TRACE1 (EAPOL, "ElExtractMPPESendRecvKeys: ElSecureEncodePw for Master Send failed with error %ld",
                            dwRetCode);
                    break;
                }

                if (pPCB->MPPESendKey.cbData != 0)
                {
                    FREE (pPCB->MPPESendKey.pbData);
                    pPCB->MPPESendKey.cbData = 0;
                    pPCB->MPPESendKey.pbData = NULL;
                }

                if ((dwRetCode = ElSecureEncodePw (
                                ((BYTE*)(pAttributeSendKey->Value))+9,
                                ulSendKeyLength,
                                &(pPCB->MPPESendKey)
                            )) != NO_ERROR)
                {
                    TRACE1 (EAPOL, "ElExtractMPPESendRecvKeys: ElSecureEncodePw for MPPESend failed with error %ld",
                            dwRetCode);
                    break;
                }
            }
            if (ulRecvKeyLength != 0)
            {
                if (pPCB->MasterSecretRecv.cbData != 0)
                {
                    FREE (pPCB->MasterSecretRecv.pbData);
                    pPCB->MasterSecretRecv.cbData = 0;
                    pPCB->MasterSecretRecv.pbData = NULL;
                }

                if ((dwRetCode = ElSecureEncodePw (
                                ((BYTE*)(pAttributeRecvKey->Value))+9,
                                ulRecvKeyLength,
                                &(pPCB->MasterSecretRecv)
                            )) != NO_ERROR)
                {
                    TRACE1 (EAPOL, "ElExtractMPPESendRecvKeys: ElSecureEncodePw for Master Recv failed with error %ld",
                            dwRetCode);
                    break;
                }

                if (pPCB->MPPERecvKey.cbData != 0)
                {
                    FREE (pPCB->MPPERecvKey.pbData);
                    pPCB->MPPERecvKey.cbData = 0;
                    pPCB->MPPERecvKey.pbData = NULL;
                }

                if ((dwRetCode = ElSecureEncodePw (
                                ((BYTE*)(pAttributeRecvKey->Value))+9,
                                ulRecvKeyLength,
                                &(pPCB->MPPERecvKey)
                            )) != NO_ERROR)
                {
                    TRACE1 (EAPOL, "ElExtractMPPESendRecvKeys: ElSecureEncodePw for MPPERecv failed with error %ld",
                            dwRetCode);
                    break;
                }
            }

            TRACE0 (EAPOL,"MPPE-Send/Recv-Keys derived by supplicant");
        }
        else
        {
            TRACE0 (EAPOL, "ElExtractMPPESendRecvKeys: pAttributeSendKey or pAttributeRecvKey == NULL");
        }

    } while (FALSE);

    if (dwRetCode != NO_ERROR)
    {
        if (pPCB->MasterSecretSend.cbData != 0)
        {
            FREE (pPCB->MasterSecretSend.pbData);
            pPCB->MasterSecretSend.cbData = 0;
            pPCB->MasterSecretSend.pbData = NULL;
        }
        if (pPCB->MasterSecretRecv.cbData != 0)
        {
            FREE (pPCB->MasterSecretRecv.pbData);
            pPCB->MasterSecretRecv.cbData = 0;
            pPCB->MasterSecretRecv.pbData = NULL;
        }
        if (pPCB->MPPESendKey.cbData != 0)
        {
            FREE (pPCB->MPPESendKey.pbData);
            pPCB->MPPESendKey.cbData = 0;
            pPCB->MPPESendKey.pbData = NULL;
        }
        if (pPCB->MPPERecvKey.cbData != 0)
        {
            FREE (pPCB->MPPERecvKey.pbData);
            pPCB->MPPERecvKey.cbData = 0;
            pPCB->MPPERecvKey.pbData = NULL;
        }
    }

    return( dwRetCode );

}


//
// ElProcessEapSuccess
//
// Description:
//
// Function called when an EAP_Success is received in any state
//
// Input arguments:
//  pPCB - Pointer to PCB for the port on which data is being processed
//  pEapolPkt - Pointer to EAPOL packet that was received
//              
// Return values:
//      NO_ERROR - success
//      non-zero - error
//

DWORD
ElProcessEapSuccess (
    IN EAPOL_PCB        *pPCB,
    IN EAPOL_PACKET     *pEapolPkt
    )
{
    EAPOL_ZC_INTF   ZCData;
    DWORD           dwRetCode = NO_ERROR;

    TRACE0 (EAPOL, "ElProcessEapSuccess: Got EAPCODE_Success");

    do
    {

        // Indicate to EAP=Dll to cleanup completed session
        if ((dwRetCode = ElEapEnd (pPCB)) != NO_ERROR)
        {
            TRACE1 (EAPOL, "ProcessReceivedPacket: EapSuccess: Error in ElEapEnd = %ld",
                    dwRetCode);
            break;
        }

        TRACE0 (EAPOL, "ElProcessEapSuccess: Authentication successful");

        // Complete remaining processing i.e. DHCP
        if ((dwRetCode = FSMAuthenticated (pPCB,
                                    pEapolPkt)) != NO_ERROR)
        {
            break;
        }

#ifdef ZEROCONFIG_LINKED

        // Indicate to WZC that authentication succeeded and
        // reset the blob it stores for the current SSID
        ZeroMemory ((PVOID)&ZCData, sizeof(EAPOL_ZC_INTF));
        ZCData.dwAuthFailCount = 0;
        ZCData.PreviousAuthenticationType = EAPOL_UNAUTHENTICATED_ACCESS;
        if (pPCB->pSSID != NULL)
        {
            memcpy (ZCData.bSSID, pPCB->pSSID->Ssid, pPCB->pSSID->SsidLength);
            ZCData.dwSizeOfSSID = pPCB->pSSID->SsidLength;
        }

        if ((dwRetCode = ElZeroConfigNotify (
                        pPCB->dwZeroConfigId,
                        WZCCMD_CFG_SETDATA,
                        pPCB->pwszDeviceGUID,
                        &ZCData
                        )) != NO_ERROR)
        {
            TRACE1 (EAPOL, "ElProcessEapSuccess: ElZeroConfigNotify failed with error %ld",
                    dwRetCode);
            dwRetCode = NO_ERROR;
        }
            
        TRACE1 (EAPOL, "ElProcessEapSuccess: Called ElZeroConfigNotify with type=(%ld)",
                    WZCCMD_CFG_SETDATA);

#endif // ZEROCONFIG_LINKED

        ElNetmanNotify (pPCB, EAPOL_NCS_AUTHENTICATION_SUCCEEDED, NULL);

    } 
    while (FALSE);

    return dwRetCode;
}


//
// ElProcessEapFail
//
// Description:
//
// Function called when an EAP_Fail is received in any state
//
// Input arguments:
//  pPCB - Pointer to PCB for the port on which data is being processed
//  pEapolPkt - Pointer to EAPOL packet that was received
//              
// Return values:
//      NO_ERROR - success
//      non-zero - error
//

DWORD
ElProcessEapFail (
    IN EAPOL_PCB        *pPCB,
    IN EAPOL_PACKET     *pEapolPkt
    )
{
    EAPOL_ZC_INTF   ZCData;
    DWORD           dwRetCode = NO_ERROR;

    TRACE0 (EAPOL, "ElProcessEapFail: Got EAPCODE_Failure");

    do
    {
        // Indicate to EAP-Dll to cleanup completed session
        if ((dwRetCode = ElEapEnd (pPCB)) != NO_ERROR)
        {
            TRACE1 (EAPOL, "ElProcessEapFail: EapFail: Error in ElEapEnd = %ld",
                    dwRetCode);
            break;
        }

        // Show failure balloon before notifying ZeroConfig
        // ZeroConfig may require to pop-up its own balloon, and that has
        // to be given preference

        ElNetmanNotify (pPCB, EAPOL_NCS_AUTHENTICATION_FAILED, NULL);

#ifdef ZEROCONFIG_LINKED

        // Indicate to WZC that authentication failed
        ZeroMemory ((PVOID)&ZCData, sizeof(EAPOL_ZC_INTF));
        ZCData.dwAuthFailCount = pPCB->dwAuthFailCount + 1;
        ZCData.PreviousAuthenticationType = pPCB->PreviousAuthenticationType;
        if (pPCB->pSSID != NULL)
        {
            memcpy (ZCData.bSSID, pPCB->pSSID->Ssid, pPCB->pSSID->SsidLength);
            ZCData.dwSizeOfSSID = pPCB->pSSID->SsidLength;
        }
        // We notify ZC before going through held state, where fail count is
        // upped. Hence, here we explicitly up it by one
        if ((dwRetCode = ElZeroConfigNotify (
                        pPCB->dwZeroConfigId,
                        ((pPCB->dwAuthFailCount+1) < pPCB->dwTotalMaxAuthFailCount)?WZCCMD_CFG_NEXT:WZCCMD_CFG_DELETE,
                        pPCB->pwszDeviceGUID,
                        &ZCData
                        )) != NO_ERROR)
        {
            TRACE1 (EAPOL, "ElProcessEapFail: ElZeroConfigNotify failed with error %ld",
                    dwRetCode);
            dwRetCode = NO_ERROR;
        }
            
        TRACE3 (EAPOL, "ElProcessEapFail: Called ElZeroConfigNotify with failcount = %ld, prevauthtype = %ld, type=(%ld)",
                    ZCData.dwAuthFailCount, 
                    ZCData.PreviousAuthenticationType,
                    ((pPCB->dwAuthFailCount+1) < pPCB->dwTotalMaxAuthFailCount)?WZCCMD_CFG_NEXT:WZCCMD_CFG_DELETE
                    );

#endif // ZEROCONFIG_LINKED

        if ((dwRetCode = FSMHeld (pPCB, NULL)) != NO_ERROR)
        {
            break;
        }
    } 
    while (FALSE);

    return dwRetCode;
}


//
// ElSetEAPOLKeyReceivedTimer
//
// Description:
//
// Function called for wireless interface when it enter AUTHENTICATED state
// If no EAPOL-Key message is received for the transmit key in the meanwhile
// the association should be negated to Zero-Config
//
// Input arguments:
//  pPCB - Pointer to PCB for the port which entered AUTHENTICATED state
//              
// Return values:
//      NO_ERROR - success
//      non-zero - error
//

DWORD
ElSetEAPOLKeyReceivedTimer (
    IN EAPOL_PCB        *pPCB
    )
{
    DWORD   dwRetCode = NO_ERROR;

    do
    {
        if (pPCB->fTransmitKeyReceived)
        {
            TRACE0 (EAPOL, "EAPOL-Key for transmit key received before entering AUTHENTICATED state");
            break;
        }

        RESTART_TIMER (pPCB->hTimer,
                EAPOL_TRANSMIT_KEY_INTERVAL,
                "PCB",
                &dwRetCode);
            
        if (dwRetCode != NO_ERROR)
        {
            TRACE1 (EAPOL, "ElSetEAPOLKeyReceivedTimer: Error in RESTART_TIMER %ld",
                    dwRetCode);
            break;
        }
        SET_TRANSMIT_KEY_TIMER(pPCB);
    }
    while (FALSE);

    return dwRetCode;
}


//
// ElVerifyEAPOLKeyReceived
//
// Description:
//
// Function called on timeout to verify if EAPOL-transmit key was received
// If no EAPOL-Key message is received for the transmit key in the meanwhile
// the association should be negated to Zero-Config
//
// Input arguments:
//  pPCB - Pointer to PCB for the port which entered AUTHENTICATED state
//              
// Return values:
//      NO_ERROR - success
//      non-zero - error
//

DWORD
ElVerifyEAPOLKeyReceived (
    IN EAPOL_PCB        *pPCB
    )
{
    EAPOL_ZC_INTF   ZCData;
    DWORD   dwRetCode = NO_ERROR;

    do
    {
        if (!pPCB->fTransmitKeyReceived)
        {
            TRACE1 (EAPOL, "EAPOL-Key for transmit key *NOT* received within %ld seconds in AUTHENTICATED state",
                    EAPOL_TRANSMIT_KEY_INTERVAL
                    ); 

            DbLogPCBEvent (DBLOG_CATEG_ERR, pPCB, EAPOL_NOT_RECEIVED_XMIT_KEY);

#ifdef ZEROCONFIG_LINKED
            // Indicate to WZC that authentication didn't really complete
            // since there was EAPOL-Key packet for the transmit key
            // Fail the entire configuration
            ZeroMemory ((PVOID)&ZCData, sizeof(EAPOL_ZC_INTF));
            ZCData.dwAuthFailCount = pPCB->dwTotalMaxAuthFailCount;
            pPCB->dwAuthFailCount = pPCB->dwTotalMaxAuthFailCount;
            ZCData.PreviousAuthenticationType = pPCB->PreviousAuthenticationType;
            if (pPCB->pSSID != NULL)
            {
                memcpy (ZCData.bSSID, pPCB->pSSID->Ssid, pPCB->pSSID->SsidLength);
                ZCData.dwSizeOfSSID = pPCB->pSSID->SsidLength;
            }
            if ((dwRetCode = ElZeroConfigNotify (
                            pPCB->dwZeroConfigId,
                            ((pPCB->dwAuthFailCount) < pPCB->dwTotalMaxAuthFailCount)?WZCCMD_CFG_NEXT:WZCCMD_CFG_DELETE,
                            pPCB->pwszDeviceGUID,
                            &ZCData
                            )) != NO_ERROR)
            {
                TRACE1 (EAPOL, "ElVerifyEAPOLKeyReceived: ElZeroConfigNotify failed with error %ld",
                        dwRetCode);
                dwRetCode = NO_ERROR;
            }
            
            TRACE3 (EAPOL, "ElVerifyEAPOLKeyReceived: Called ElZeroConfigNotify with failcount = %ld, prevauthtype = %ld, type=(%ld)",
                        ZCData.dwAuthFailCount, 
                        ZCData.PreviousAuthenticationType,
                        ((pPCB->dwAuthFailCount+1) < pPCB->dwTotalMaxAuthFailCount)?WZCCMD_CFG_NEXT:WZCCMD_CFG_DELETE
                        );

            // If authfailed limit reached, go to Disconnected state
            if (pPCB->dwAuthFailCount >= pPCB->dwTotalMaxAuthFailCount)
            {
                TRACE2 (EAPOL, "ElVerifyEAPOLKeyReceived: Pushing into disconnected state: Fail count (%ld) > Max fail count (%ld)",
                        pPCB->dwAuthFailCount, pPCB->dwTotalMaxAuthFailCount);
                FSMDisconnected (pPCB, NULL);
            }
    
#endif // ZEROCONFIG_LINKED
        }
        else
        {
            TRACE1 (EAPOL, "EAPOL-Key for transmit key received within %ld seconds in AUTHENTICATED state",
                    EAPOL_TRANSMIT_KEY_INTERVAL
                    ); 
        }
    }
    while (FALSE);

    return dwRetCode;
}