/*++

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

#define EAPOL_SERVICE

#ifndef EAPOL_SERVICE

HRESULT 
EAPOLMANAuthenticationStarted (
        REFGUID InterfaceId
);

HRESULT 
EAPOLMANAuthenticationSucceeded (
        REFGUID InterfaceId
);

HRESULT 
EAPOLMANAuthenticationFailed ( 
        REFGUID InterfaceId,
        DWORD dwType
);

HRESULT EAPOLMANNotification(
        REFGUID InterfaceId,
        LPWSTR szwNotificationMessage,
        DWORD dwType
);

#endif

//
// 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:
//

VOID 
ElProcessReceivedPacket (
        IN  PVOID   pvContext
        )
{
    EAPOL_PCB       *pPCB = NULL;
    EAPOL_BUFFER    *pEapolBuffer = NULL;
    DWORD           dwLength = 0;
    ETH_HEADER      *pEthHdr = NULL;
    EAPOL_PACKET    *pEapolPkt = NULL;
    EAPOL_PACKET_D8 *pEapolPktD8 = NULL;
    BOOLEAN         fRemoteEnd8021XD8 = FALSE;
    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;
    EAPOL_PACKET_D8_D7      DummyHeader;
    DWORD           dwRetCode = NO_ERROR;


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

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

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

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

        ACQUIRE_WRITE_LOCK (&(pPCB->rwLock));
        if (!EAPOL_PORT_ACTIVE(pPCB))
        {
            TRACE1 (EAPOL, "ProcessReceivedPacket: Port %s not active",
                    pPCB->pszDeviceGUID);
            RELEASE_WRITE_LOCK (&(pPCB->rwLock));
            FREE (pEapolBuffer);
            break;
        }
        RELEASE_WRITE_LOCK (&(pPCB->rwLock));

        // 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));
            FREE (pEapolBuffer);
            dwRetCode =  ERROR_INVALID_PACKET_LENGTH_OR_ID;
            break;
        }

        // Validate Destination MAC Address
        // Compare with MAC address got during MEDIA_CONNECT

#if 0
        pEthHdr = (ETH_HEADER *)pBuffer;
        if ((memcmp ((BYTE *)pEthHdr->bSrcAddr, 
                        (BYTE *)pPCB->bDestMacAddr, 
                        SIZE_MAC_ADDR)) != 0)
        {
            TRACE2 (EAPOL, "ProcessReceivedPacket: Dest MAC address %s does not match PAE address %s. Ignoring packet",
                    pEthHdr->SrcAddr,
                    pPCB->bDestMacAddr);
            FREE (pEapolBuffer);
            dwRetCode = ERROR_INVALID_ADDRESS;
            break;
        }
#endif

        // 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);
        }
        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)
        {
            TRACE2 (EAPOL, "ProcessReceivedPacket: Packet PAE type %s does not match expected type %s. Ignoring packet",
                    pEapolPkt->EthernetType,
                    pPCB->bEtherType);
            FREE (pEapolBuffer);
            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);
            FREE (pEapolBuffer);
            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) + sizeof (EAPOL_PACKET)))
            {
                TRACE1 (EAPOL, "ProcessReceivedPacket: Invalid length of EAP packet %d. Ignoring packet",
                        dwLength);
                FREE (pEapolBuffer);
                dwRetCode = ERROR_INVALID_PACKET_LENGTH_OR_ID;
                break;
            }


            // Determine if the packet is draft 8 or not

            pEapolPktD8 = (EAPOL_PACKET_D8 *)pEapolPkt;

            pEapPkt = (PPP_EAP_PACKET *)pEapolPktD8->PacketBody;


            switch (WireToHostFormat16(pEapolPktD8->AuthResultCode))
            {
                case AUTH_Continuing:
                    if (pEapPkt->Code == EAPCODE_Request)
                    {
                        fRemoteEnd8021XD8 = TRUE;
                    }
                    break;

                case AUTH_Authorized:
                    if (pEapPkt->Code ==  EAPCODE_Success)

                    {
                        fRemoteEnd8021XD8 = TRUE;
                    }
                    break;

                case AUTH_Unauthorized:
                    if ((pEapPkt->Code ==  EAPCODE_Failure) ||
                        (pEapPkt->Code ==  EAPCODE_Success))
                    {
                        fRemoteEnd8021XD8 = TRUE;
                    }
                    break;
            }

            if (fRemoteEnd8021XD8 && (WireToHostFormat16(pEapolPktD8->PacketBodyLength) != 0))
            {
                TRACE0 (EAPOL, "ProcessReceivedPacket: Packet received DRAFT 8 format");
                pPCB->fRemoteEnd8021XD8 = TRUE;

                memcpy (DummyHeader.AuthResultCode, pEapolPktD8->AuthResultCode,
                        2);
                memcpy (DummyHeader.EthernetType, pEapolPktD8->EthernetType,
                        2);
                DummyHeader.ProtocolVersion = pEapolPktD8->ProtocolVersion;
                DummyHeader.PacketType = pEapolPktD8->PacketType;

                memcpy ((BYTE *)pEapolPktD8, (BYTE *)&DummyHeader, 6);

                pEapolPkt = (EAPOL_PACKET *)((BYTE *)pEapolPktD8 + 2);
            }
            else
            {
                pPCB->fRemoteEnd8021XD8 = FALSE;
                TRACE0 (EAPOL, "ProcessReceivedPacket: Packet received PRE-DRAFT 8 format");
            }

            pEapPkt = (PPP_EAP_PACKET *)pEapolPkt->PacketBody;

            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);
                    FREE (pEapolBuffer);
                    dwRetCode = ERROR_INVALID_PACKET_LENGTH_OR_ID;
                    break;
                }
                if (pEapPkt->Data[0] == EAPTYPE_Identity)
                {
                    pPCB->fIsRemoteEndEAPOLAware = TRUE;
                    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);
                FREE (pEapolBuffer);
                dwRetCode = ERROR_INVALID_PACKET;
                break;
            }
        }
        else
        {
            TRACE0 (EAPOL, "ProcessReceivedPacket: != EAP_Packet");
            if (pEapolPkt->PacketType == EAPOL_Key)
            {
                TRACE0 (EAPOL, "ProcessReceivedPacket: == EAPOL_Key");
                RxKey = TRUE;
            
                // Determine if the packet is draft 8 or not

                pEapolPktD8 = (EAPOL_PACKET_D8 *)pEapolPkt;

                // In pre-draft 8, PacketBodyLength cannot be '0'
                // If it is zero, it is draft 8 packet format

                if (WireToHostFormat16(pEapolPktD8->AuthResultCode)
                        == AUTH_Continuing)
                {
                    pPCB->fRemoteEnd8021XD8 = TRUE;

                    memcpy (DummyHeader.AuthResultCode, 
                            pEapolPktD8->AuthResultCode,
                            2);

                    memcpy (DummyHeader.EthernetType, 
                            pEapolPktD8->EthernetType,
                            2);

                    DummyHeader.ProtocolVersion = pEapolPktD8->ProtocolVersion;
                    DummyHeader.PacketType = pEapolPktD8->PacketType;

                    memcpy ((BYTE *)pEapolPktD8, (BYTE *)&DummyHeader, 6);

                    pEapolPkt = (EAPOL_PACKET *)((BYTE *)pEapolPktD8 + 2);
                }
                else
                {
                    pPCB->fRemoteEnd8021XD8 = FALSE;
                    TRACE0 (EAPOL, "ProcessReceivedPacket: EAPOL_Key Packet received PRE-DRAFT 8 format");
                }
            }
        }

        //
        // NOTE:
        // Should we check values of EAP type
        //

        // Checking value of PCB fields now
        ACQUIRE_WRITE_LOCK (&(pPCB->rwLock));

        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, Ignoing packet");
                break;

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

            case EAPOLSTATE_CONNECTING:
                TRACE0 (EAPOL, "ProcessReceivedPacket: EAPOLSTATE_CONNECTING");
                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 (ReqId | ReqAuth | 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;
                    }
                }
                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;
                        }
                    }
                }

                // Continue further processing

                if (EapSuccess | EapFail)
                {
                    // 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 (RxKey)
                {
                    if ((dwRetCode = FSMRxKey (pPCB,
                                                pEapolPkt)) != NO_ERROR)
                    {
                        break;
                    }
                }
                break;

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

        // Only packet passing through switch statement will be freed here
        FREE (pEapolBuffer);

        RELEASE_WRITE_LOCK (&(pPCB->rwLock));

    } while (FALSE);

    
    // Post a new read request, ignoring errors
            
    ACQUIRE_WRITE_LOCK (&(pPCB->rwLock));
    if (!EAPOL_PORT_ACTIVE(pPCB))
    {
        TRACE1 (EAPOL, "ProcessReceivedPacket: Port %s not active, not reposting read request",
                pPCB->pszDeviceGUID);
        // Port is not active, release Context buffer
        RELEASE_WRITE_LOCK (&(pPCB->rwLock));
    }
    else
    {
        TRACE1 (EAPOL, "ProcessReceivedPacket: Reposting buffer on port %s",
                pPCB->pszDeviceGUID);
        RELEASE_WRITE_LOCK (&(pPCB->rwLock));
        
        // 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);
            // LOG
        }
    }
    
    // Dereference ref count held for the read that was just processed
    EAPOL_DEREFERENCE_PORT(pPCB);
    TRACE2 (EAPOL, "ProcessReceivedPacket: pPCB= %p, RefCnt = %ld", 
            pPCB, pPCB->dwRefCount);

    TRACE0 (EAPOL, "ProcessReceivedPacket exit");

    return;
}


// 
// 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
        )
{
    DWORD           dwRetCode   = NO_ERROR;

    TRACE1 (EAPOL, "FSMDisconnected entered for port %s", pPCB->pszFriendlyName);

    do 
    {

    } while (FALSE);

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

    pPCB->State = EAPOLSTATE_DISCONNECTED;

    // Free Identity buffer

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

    // Free Password buffer

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

    // 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;
    }

    // Free SSID

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

    pPCB->fGotUserIdentity = FALSE;

    TRACE1 (EAPOL, "FSMDisconnected completed for port %s", pPCB->pszFriendlyName);

    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
        )
{
    EAPOL_PACKET    *pEapolPkt  = NULL;
    DWORD           dwRetCode   = NO_ERROR;

    TRACE1 (EAPOL, "FSMLogoff entered for port %s", pPCB->pszFriendlyName);

    do 
    {
        // 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 %s", pPCB->pszFriendlyName);

    pPCB->State = EAPOLSTATE_LOGOFF;

    // Free Identity buffer

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

    // Free Password buffer

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

    // 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;
    }

    // Free SSID

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

    pPCB->fGotUserIdentity = FALSE;

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

    TRACE1 (EAPOL, "FSMLogoff completed for port %s", pPCB->pszFriendlyName);

    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
        )
{
    EAPOL_PACKET    *pEapolPkt = NULL;
    DWORD           dwStartInterval = 0;               
    GUID            DeviceGuid;
    DWORD           dwRetCode = NO_ERROR;

    TRACE1 (EAPOL, "FSMConnecting entered for port %s", pPCB->pszFriendlyName);

#ifndef EAPOL_SERVICE

    ElStringToGuid (pPCB->pszDeviceGUID, &DeviceGuid);
    (VOID)EAPOLMANAuthenticationStarted (&DeviceGuid);

#endif

    do 
    {
        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;
                }

#ifndef EAPOL_SERVICE

                // Display change of status using sys tray balloon
                // on interface icon
                ElStringToGuid (pPCB->pszDeviceGUID, &DeviceGuid);
                (VOID)EAPOLMANAuthenticationSucceeded (&DeviceGuid);

#endif

                // No need to send out more EAPOL_Start packets

                // Reset start packet count
                pPCB->ulStartCount = 0;
                pPCB->fIsRemoteEndEAPOLAware = FALSE;
                break;
            }
        }
        else
        {
            pPCB->ulStartCount++;
        }
            
        // 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 (!pPCB->fUserLoggedIn)

        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
        // 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;
        }

        // ISSUE:
        // Does Authenticator side also ignore data beyond PacketType
        // as the supplicant side does?

        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 %s", pPCB->pszFriendlyName);

        pPCB->State = EAPOLSTATE_CONNECTING;
        SET_EAPOL_START_TIMER(pPCB);

    } while (FALSE);

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

    TRACE1 (EAPOL, "FSMConnecting completed for port %s", pPCB->pszFriendlyName);
    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 %s", pPCB->pszFriendlyName);

#ifndef EAPOL_SERVICE

    ElStringToGuid (pPCB->pszDeviceGUID, &DeviceGuid);
    (VOID)EAPOLMANAuthenticationStarted (&DeviceGuid);

#endif

    do
    {

        // 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;
        }

        // 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, "FSMAcquired: Error in RESTART_TIMER %ld",
                    dwRetCode);
            break;
        }

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


        // 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
        {

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

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

        SET_EAPOL_AUTH_TIMER(pPCB);
        pPCB->State = EAPOLSTATE_ACQUIRED;

    } while (FALSE);

    TRACE1 (EAPOL, "FSMAcquired completed for port %s", pPCB->pszFriendlyName);

    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 %s", pPCB->pszFriendlyName);

#ifndef EAPOL_SERVICE

    ElStringToGuid (pPCB->pszDeviceGUID, &DeviceGuid);
    (VOID)EAPOLMANAuthenticationStarted (&DeviceGuid);

#endif

    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, "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
        {
            // 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 %s", pPCB->pszFriendlyName);

        SET_EAPOL_AUTH_TIMER(pPCB);
        pPCB->State = EAPOLSTATE_AUTHENTICATING;

    } while (FALSE);

    TRACE1 (EAPOL, "FSMAuthenticating completed for port %s", pPCB->pszFriendlyName);

    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
        )
{
    DWORD       dwRetCode = NO_ERROR;

    TRACE1 (EAPOL, "FSMHeld entered for port %s", pPCB->pszFriendlyName);

    do 
    {
#ifdef DRAFT7
        if (g_dwMachineAuthEnabled)
        {
#endif

        pPCB->dwAuthFailCount++;

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

            // Restart timer with heldPeriod
            RESTART_TIMER (pPCB->hTimer,
                    pPCB->EapolConfig.dwheldPeriod,
                    "PCB",
                    &dwRetCode);
        }
        else
        {
            TRACE1 (EAPOL, "Restarting Held timer with extended time value = %ld",
                    (pPCB->dwAuthFailCount * (pPCB->EapolConfig.dwheldPeriod)));

            // Restart timer with heldPeriod times pPCB->dwAuthFailCount
            RESTART_TIMER (pPCB->hTimer,
                    ((pPCB->dwAuthFailCount) * (pPCB->EapolConfig.dwheldPeriod)),
                    "PCB",
                    &dwRetCode);
        }

#ifdef DRAFT7
        }
        else
        {

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

        // Restart timer with heldPeriod
        RESTART_TIMER (pPCB->hTimer,
                pPCB->EapolConfig.dwheldPeriod,
                "PCB",
                &dwRetCode);

        } // g_dwMachineAuthEnabled
#endif
            
        if (dwRetCode != NO_ERROR)
        {
            TRACE1 (EAPOL, "FSMHeld: Error in RESTART_TIMER %ld",
                    dwRetCode);

            break;
        }

        // Free Identity buffer

        if (pPCB->pszIdentity != NULL)
        {
            FREE (pPCB->pszIdentity);
            pPCB->pszIdentity = NULL;
        }
    
        // Free Password buffer
    
        if (pPCB->pszPassword != NULL)
        {
            FREE (pPCB->pszPassword);
            pPCB->pszPassword = NULL;
        }
    
        // 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;
        }
    
        // 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;
    
        TRACE1 (EAPOL, "Setting state HELD for port %s", pPCB->pszFriendlyName);

        pPCB->State = EAPOLSTATE_HELD;
        SET_EAPOL_HELD_TIMER(pPCB);

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

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

    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;
    DWORD               dwRetCode = NO_ERROR;

    TRACE1 (EAPOL, "FSMAuthenticated entered for port %s", 
            pPCB->pszFriendlyName);

    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 call DHCP

        // if (pPCB->ulStartCount < pPCB->EapolConfig.dwmaxStart)
        {
            // Call DHCP to do PnP
            ZeroMemory(&DhcpPnpChange, sizeof(DHCP_PNP_CHANGE));
            DhcpPnpChange.Version = DHCP_PNP_CHANGE_VERSION_0;
            if ((dwRetCode = DhcpHandlePnPEvent(0, 
                        DHCP_CALLER_TCPUI, 
                        NULL,
                        //pPCB->pszDeviceGUID,
                        &DhcpPnpChange, 
                        NULL)) != NO_ERROR)
            {
                TRACE1 (EAPOL, "FSMAuthenticated: DHCPHandlePnPEvent returned error %ld",
                        dwRetCode);
                break;
            }
            TRACE0 (EAPOL, "FSMAuthenticated: DHCPHandlePnPEvent successful");
        }
            
        TRACE1 (EAPOL, "Setting state AUTHENTICATED for port %s", pPCB->pszFriendlyName);

        pPCB->State = EAPOLSTATE_AUTHENTICATED;

    } while (FALSE);

    TRACE1 (EAPOL, "FSMAuthenticated completed for port %s", pPCB->pszFriendlyName);

    return dwRetCode;
}


//
// FSMRxKey
//
// Description:
//      Function called when an EAPOL-Key packet is received in the 
//      Authenticated state. 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
FSMRxKey (
        IN  EAPOL_PCB       *pPCB,
        IN  EAPOL_PACKET   *pEapolPkt
        )
{
    EAPOL_KEY_DESC      *pKeyDesc = NULL;
    EAPOL_KEY_DESC_D8   *pKeyDesc_D8 = NULL;
    EAPOL_PACKET_D8     EapolPktD8;
    EAPOL_PACKET_D8     *pEapolPktD8 = NULL;
    ULONGLONG           ullReplayCheck = 0; 
    BYTE                bReplayCheck[8];
    BYTE                *pbMD5EapolPkt = NULL;
    DWORD               dwMD5EapolPktLen = 0;
    MD5_CTX             MD5Context;
    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;

    DWORD               dwRetCode = NO_ERROR;

    TRACE1 (EAPOL, "FSMRxKey entered for port %s", pPCB->pszFriendlyName);

    do
    {
        if (!pPCB->fRemoteEnd8021XD8)
        {
        // DRAFT 7

        pKeyDesc = (EAPOL_KEY_DESC *)pEapolPkt->PacketBody;

        dwKeyLength = WireToHostFormat16 (pKeyDesc->KeyLength);

        TRACE4 (EAPOL, "Signature Type = %ld, \n Encrypt Type = %ld, \n KeyLength = %ld, \n KeyIndex = %ld",
                pKeyDesc->SignatureType,
                pKeyDesc->EncryptType,
                dwKeyLength,
                pKeyDesc->KeyIndex
                );

        // For Draft 8, do not check for non-existing fields

        if (pKeyDesc->SignatureType != 1)
        {
            TRACE1 (EAPOL, "FSMRxKey: Invalid signature type = %ld",
                    pKeyDesc->SignatureType);
            // log
            break;
        }

        if (pKeyDesc->EncryptType != 1)
        {
            TRACE1 (EAPOL, "FSMRxKey: Invalid encryption type = %ld",
                    pKeyDesc->EncryptType);
            // log
            break;
        }

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

        ullReplayCheck = ((*((PBYTE)(bReplayCheck)+0) << 56) +
                         (*((PBYTE)(bReplayCheck)+1) << 48) +
                         (*((PBYTE)(bReplayCheck)+2) << 40) +
                         (*((PBYTE)(bReplayCheck)+3) << 32) +
                         (*((PBYTE)(bReplayCheck)+4) << 24) +
                         (*((PBYTE)(bReplayCheck)+5) << 16) +
                         (*((PBYTE)(bReplayCheck)+6) << 8) +
                         (*((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, "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, "FSMRxKey: Replay counter is not in sync, something is wrong");
            // log
            break;
        }
        
        // If valid ReplayCounter, save it in the PCB for future check
        pPCB->ullLastReplayCounter = ullReplayCheck;


        TRACE0 (EAPOL, "Replay counter in desc ======");
        EAPOL_DUMPBA (pKeyDesc->ReplayCounter, 8);

        //
        // 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, "FSMRxKey: Error in MALLOC for pbMD5EapolPkt");
            dwRetCode = ERROR_NOT_ENOUGH_MEMORY;
            break;
        }

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

        //
        // 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,
            pPCB->pbMPPERecvKey,
            pPCB->dwMPPERecvKeyLength,
            bHMACMD5HashBuffer
            );

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

        TRACE0 (EAPOL, "FSMRxKey: MD5 Hash secret ==");
        EAPOL_DUMPBA (pPCB->pbMPPERecvKey, pPCB->dwMPPERecvKeyLength);

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

        TRACE0 (EAPOL, "FSMRxKey: 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, "FSMRxKey: Signature in Key Desc does not match, potential security attack");
            // log
            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 *)pPCB->pbMPPESendKey, 32);

            rc4_key (&rc4key, 48, 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
        {
            // Use the MPPESend key as the encryption key

            pbKeyToBePlumbed = (BYTE *)pPCB->pbMPPESendKey;
        }

        if ((pNdisWEPKey = MALLOC ( sizeof(NDIS_802_11_WEP)-1+dwKeyLength )) 
                == NULL)
        {
            TRACE0 (EAPOL, "FSMRxKey: 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, "FSMRxKey: Key Index is %x", pNdisWEPKey->KeyIndex);


        // 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, "FSMRxKey: ElNdisuioSetOIDValue failed with error %ld",
                    dwRetCode);
        }

        }
        else
        {
        // DRAFT 8

        // Point beyond Signature Type for structure alignment
        pKeyDesc_D8 = (EAPOL_KEY_DESC_D8 *)(pEapolPkt->PacketBody);

        dwKeyLength = WireToHostFormat16 (pKeyDesc_D8->KeyLength);

        TRACE3 (EAPOL, "Descriptor type = %ld, \n KeyLength = %ld, \n KeyIndex = %ld",
                pKeyDesc_D8->DescriptorType,
                dwKeyLength,
                pKeyDesc_D8->KeyIndex
                );

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

        ullReplayCheck = ((*((PBYTE)(bReplayCheck)+0) << 56) +
                         (*((PBYTE)(bReplayCheck)+1) << 48) +
                         (*((PBYTE)(bReplayCheck)+2) << 40) +
                         (*((PBYTE)(bReplayCheck)+3) << 32) +
                         (*((PBYTE)(bReplayCheck)+4) << 24) +
                         (*((PBYTE)(bReplayCheck)+5) << 16) +
                         (*((PBYTE)(bReplayCheck)+6) << 8) +
                         (*((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, "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, "FSMRxKey: Replay counter is not in sync, something is wrong");
            // log
            break;
        }
        
        // If valid ReplayCounter, save it in the PCB for future check
        pPCB->ullLastReplayCounter = ullReplayCheck;

        TRACE1 (EAPOL, "Replay counter ======= %lx", ullReplayCheck);

        TRACE0 (EAPOL, "Replay counter in desc ======");
        EAPOL_DUMPBA (pKeyDesc_D8->ReplayCounter, 8);

        //
        // 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
        //

        {
            ZeroMemory (&EapolPktD8, sizeof (EAPOL_PACKET_D8));

            memcpy (EapolPktD8.EthernetType, pEapolPkt->EthernetType, 2);
            EapolPktD8.ProtocolVersion = pEapolPkt->ProtocolVersion;
            EapolPktD8.PacketType = pEapolPkt->PacketType;
            memcpy (EapolPktD8.PacketBodyLength, pEapolPkt->PacketBodyLength, 
                    2);
            memcpy ((BYTE *)&(EapolPktD8.AuthResultCode), (BYTE *)pEapolPkt - 2, 2);

            memcpy ((BYTE *)pEapolPkt - 2, (BYTE *)&EapolPktD8,
                    sizeof(EAPOL_PACKET_D8)-1);

            pEapolPktD8 = (EAPOL_PACKET_D8 *)((BYTE *)pEapolPkt - 2);
        }

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

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

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

        // Draft 8 has different KEY_DESC size
        ZeroMemory ((BYTE *)(pbMD5EapolPkt
                            - sizeof(pEapolPktD8->EthernetType) +
                            sizeof(EAPOL_PACKET_D8) - 1 + // pEapolPktD8->Body
                            sizeof(EAPOL_KEY_DESC_D8) - // End of EAPOL_KEY_DESC
                            MD5DIGESTLEN-1), // Signature field
                            MD5DIGESTLEN);

        (VOID) ElGetHMACMD5Digest (
            pbMD5EapolPkt,
            dwMD5EapolPktLen,
            pPCB->pbMPPERecvKey,
            pPCB->dwMPPERecvKeyLength,
            bHMACMD5HashBuffer
            );

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

        TRACE0 (EAPOL, "FSMRxKey: MD5 Hash secret ==");
        EAPOL_DUMPBA (pPCB->pbMPPERecvKey, pPCB->dwMPPERecvKeyLength);

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

        TRACE0 (EAPOL, "FSMRxKey: Signature sent in EAPOL_KEY_DESC");
        EAPOL_DUMPBA (pKeyDesc_D8->KeySignature, MD5DIGESTLEN);

        //
        // Check if HMAC-MD5 hash in received packet is what is expected
        //
        if (memcmp (bHMACMD5HashBuffer, pKeyDesc_D8->KeySignature, MD5DIGESTLEN) != 0)
        {
            TRACE0 (EAPOL, "FSMRxKey: Signature in Key Desc does not match, potential security attack");
            // log
            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 (pEapolPktD8->PacketBodyLength) > sizeof (EAPOL_KEY_DESC))

        {
            memcpy ((BYTE *)bKeyBuffer, (BYTE *)pKeyDesc_D8->Key_IV, 16);
            memcpy ((BYTE *)&bKeyBuffer[16], (BYTE *)pPCB->pbMPPESendKey, 32);

            rc4_key (&rc4key, 48, bKeyBuffer);
            rc4 (&rc4key, dwKeyLength, pKeyDesc_D8->Key);

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


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

            pbKeyToBePlumbed = pKeyDesc_D8->Key;
            
        }
        else
        {
            // Use the MPPESend key as the encryption key

            pbKeyToBePlumbed = (BYTE *)pPCB->pbMPPESendKey;
        }

        if ((pNdisWEPKey = MALLOC ( sizeof(NDIS_802_11_WEP)-1+dwKeyLength )) 
                == NULL)
        {
            TRACE0 (EAPOL, "FSMRxKey: 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_D8->KeyIndex & 0x80)
        {
            pNdisWEPKey->KeyIndex = 0x80000000;
        }
        else
        {
            pNdisWEPKey->KeyIndex = 0x00000000;
        }

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

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


        // 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, "FSMRxKey: ElNdisuioSetOIDValue failed with error %ld",
                    dwRetCode);
        }

        }
    } while (FALSE);

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

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

    TRACE1 (EAPOL, "FSMRxKey completed for port %s", pPCB->pszFriendlyName);

    return dwRetCode;
}


//
// 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 %s is inactive",
                    pPCB->pszDeviceGUID);
            break;
        }

        // Check the timer has been changed
        // If the current time is less than the programmed timeout on
        // the PCB, either timer component has shot off timer earlier
        // or the timer fired but someone changed it in the meanwhile
        if (pPCB->ulTimeout > GetTickCount())
        {
            TRACE0 (EAPOL, "ElTimeoutCallbackRoutine: Timeout value has been changed or Timer fired earlier than required");
            break;
        }
    
        // 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;
                }
                FSMConnecting(pPCB);
                break;
    
            case EAPOLSTATE_ACQUIRED:
                if (!EAPOL_AUTH_TIMER_SET(pPCB))
                {
                    TRACE1 (EAPOL, "ElTimeoutCallbackRoutine: Wrong timeout %ld in Acquired state", CHECK_EAPOL_TIMER(pPCB));
                    break;
                }
                FSMConnecting(pPCB);
                break;
                
            case EAPOLSTATE_AUTHENTICATING:
                if (!EAPOL_AUTH_TIMER_SET(pPCB))
                {
                    TRACE1 (EAPOL, "ElTimeoutCallbackRoutine: Wrong timeout %ld in Authenticating state", CHECK_EAPOL_TIMER(pPCB));
                    break;
                }
                FSMConnecting(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;
                }
                FSMConnecting(pPCB);
                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;
    WCHAR           awszNotificationMsg[MAX_NOTIFICATION_MSG_SIZE];
    DWORD           dwReceivedId = 0;
    DWORD           dwDraft8HdrIncr = 0;
    DWORD           dwRetCode = NO_ERROR;

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

    if (!(pPCB->fEapInitialized))
    {
        if (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;
    }

    if (pPCB->fRemoteEnd8021XD8)
    {
        // Account for 2 bytes of AuthResultCode
        dwDraft8HdrIncr = 2;
    }

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

    // Notification message for the user

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

        pPCB->pszEapReplyMessage = EapResult.pszReplyMessage;

        // Notify user of message

#ifndef EAPOL_SERVICE

        ZeroMemory (awszNotificationMsg, MAX_NOTIFICATION_MSG_SIZE);
        if (0 == MultiByteToWideChar (
                    CP_ACP,
                    0,
                    pPCB->pszEapReplyMessage,
                    -1,
                    awszNotificationMsg, 
                    MAX_NOTIFICATION_MSG_SIZE))
        {
            dwRetCode = GetLastError();
    
            TRACE2 (EAPOL,"MultiByteToWideChar(%s) failed: %d",
                                        pPCB->pszEapReplyMessage,
                                        dwRetCode);
            FREE (pEapolPkt);
            pEapolPkt = NULL;
                
            return dwRetCode;
            
        }

        // Display notification message using sys tray balloon
        // on interface icon
        ElStringToGuid (pPCB->pszDeviceGUID, &DeviceGuid);
        (VOID)EAPOLMANNotification (&DeviceGuid,
                                    awszNotificationMsg,
                                    0);

#endif

        TRACE1 (EAPOL, "ElEapWork: Notified user of EAP data = %s",
              pPCB->pszEapReplyMessage);
    }

    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) 
    {
        if ((dwRetCode = ElSetEapUserInfo (
                        pPCB->hUserToken,
                        pPCB->pszDeviceGUID,
                        pPCB->dwEapTypeToBeUsed,
                        pPCB->pszSSID,
                        EapResult.pUserData,
                        EapResult.dwSizeOfUserData)) != NO_ERROR)
        {
            TRACE1 (EAPOL, "ElEapWork: ElSetEapUserInfo failed with error = %d",
                    dwRetCode);
            if (pEapolPkt != NULL)
            {
                FREE (pEapolPkt);
                pEapolPkt = NULL;
            }
            return dwRetCode;
        }

        TRACE1 (EAPOL, "ElEapWork: Saved EAP data for user, dwRetCode = %d", 
                dwRetCode);
    }

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

    if ((EapResult.fSaveConnectionData ) &&
         ( 0 != EapResult.SetCustomAuthData.dwSizeOfConnectionData ) )
    {
           
        if ((dwRetCode = ElSetCustomAuthData (
                        pPCB->pszDeviceGUID,
                        pPCB->dwEapTypeToBeUsed,
                        pPCB->pszSSID,
                        EapResult.SetCustomAuthData.pConnectionData,
                        EapResult.SetCustomAuthData.dwSizeOfConnectionData
                        )) != NO_ERROR)
        {
            TRACE1 ( EAPOL, "ElEapWork: ElSetCustomAuthData failed with error = %d",
                    dwRetCode);
            FREE (pEapolPkt);
            pEapolPkt = NULL;
            return dwRetCode;
        }

        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 = 
                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");
                ASSERT(0);
            }
    
        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");

                //
                // 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");
                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;


    //
    // Every time we get encryption keys, plumb them to the driver
    //

    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);

            pPCB->dwMPPESendKeyLength = ulSendKeyLength;
            pPCB->dwMPPERecvKeyLength = ulRecvKeyLength;

            //
            // Copy MPPE Send and Receive Keys into the PCB for later usage
            // These keys will be used to decrypt the global multicast key
            // (if any).
            //

            if (pPCB->dwMPPESendKeyLength != 0)
            {
                if (pPCB->pbMPPESendKey != NULL)
                {
                    FREE (pPCB->pbMPPESendKey);
                    pPCB->pbMPPESendKey = NULL;
                }

                if ((pPCB->pbMPPESendKey = MALLOC (pPCB->dwMPPESendKeyLength))
                        == NULL)
                {
                    TRACE0 (EAPOL, "ElExtractMPPESendRecvKeys: Error in Malloc for SendKey");
                    dwRetCode = ERROR_NOT_ENOUGH_MEMORY;
                    break;
                }
                memcpy (pPCB->pbMPPESendKey, 
                        ((BYTE*)(pAttributeSendKey->Value))+9, 
                        pPCB->dwMPPESendKeyLength);
            }

            if (pPCB->dwMPPERecvKeyLength != 0)
            {
                if (pPCB->pbMPPERecvKey != NULL)
                {
                    FREE (pPCB->pbMPPERecvKey);
                    pPCB->pbMPPERecvKey = NULL;
                }

                if ((pPCB->pbMPPERecvKey = MALLOC (pPCB->dwMPPERecvKeyLength))
                        == NULL)
                {
                    TRACE0 (EAPOL, "ElExtractMPPESendRecvKeys: Error in Malloc for RecvKey");
                    dwRetCode = ERROR_NOT_ENOUGH_MEMORY;
                    break;
                }
                memcpy (pPCB->pbMPPERecvKey, 
                        ((BYTE*)(pAttributeRecvKey->Value))+9, 
                        pPCB->dwMPPERecvKeyLength);
            }

            TRACE0 (EAPOL,"MPPE-Send/Recv-Keys set");

        }
        else
        {
            TRACE0 (EAPOL, "ElExtractMPPESendRecvKeys: pAttributeSendKey or pAttributeRecvKey == NULL");
        }

    } while (FALSE);

    if (dwRetCode != NO_ERROR)
    {
        if (pPCB->pbMPPESendKey != NULL)
        {
            FREE (pPCB->pbMPPESendKey);
            pPCB->pbMPPESendKey = NULL;
        }

        if (pPCB->pbMPPERecvKey != NULL)
        {
            FREE (pPCB->pbMPPERecvKey);
            pPCB->pbMPPERecvKey = 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
    )
{
    GUID                    DeviceGuid;
    BOOLEAN                 fAuthenticateAndAuthorized = TRUE;
    EAPOL_PACKET_D8_D7      *pDummyHeader;
    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;
        }

        if (pPCB->fRemoteEnd8021XD8)
        {
            fAuthenticateAndAuthorized = FALSE;

            pDummyHeader = (EAPOL_PACKET_D8_D7 *)((BYTE *)pEapolPkt - 2);

            switch (WireToHostFormat16(pDummyHeader->AuthResultCode))
            {
                case AUTH_Authorized:
                    fAuthenticateAndAuthorized = TRUE;
                    break;
                case AUTH_Unauthorized:
                    fAuthenticateAndAuthorized = FALSE;
                    break;
                default:
                    fAuthenticateAndAuthorized = FALSE;
                    break;
            }
        }

        if (fAuthenticateAndAuthorized)
        {
            TRACE0 (EAPOL, "ElProcessEapSuccess: Autho and Authen successful");

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

#ifndef EAPOL_SERVICE

            // Display change of status using sys tray balloon
            // on interface icon
            ElStringToGuid (pPCB->pszDeviceGUID, &DeviceGuid);
            (VOID)EAPOLMANAuthenticationSucceeded (&DeviceGuid);

#endif
        }
        else
        {
            TRACE0 (EAPOL, "ElProcessEapSuccess: Autho and Authen failed");

            if ((dwRetCode = FSMHeld (pPCB)) != NO_ERROR)
            {
                break;
            }

#ifndef EAPOL_SERVICE

            // Display change of status using sys tray balloon
            // on interface icon
            ElStringToGuid (pPCB->pszDeviceGUID, &DeviceGuid);
            (VOID)EAPOLMANAuthenticationFailed (&DeviceGuid, 0);
    
#endif
        }

    } 
    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
    )
{
    GUID        DeviceGuid;
    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;
        }

        if ((dwRetCode = FSMHeld (pPCB)) != NO_ERROR)
        {
            break;
        }

#ifndef EAPOL_SERVICE

        // Display change of status using sys tray balloon
        // on interface icon
        ElStringToGuid (pPCB->pszDeviceGUID, &DeviceGuid);
        (VOID)EAPOLMANAuthenticationFailed (&DeviceGuid, 0);

#endif

    } 
    while (FALSE);

    return dwRetCode;
}

#undef EAPOL_SERVICE