/********************************************************************/
/**               Copyright(c) 1989 Microsoft Corporation.         **/
/********************************************************************/

//***
//
// Filename:    auth.c
//
// Description: Contains FSM code to handle and authentication protocols.
//
// History:
//          Nov 11,1993.    NarenG          Created original version.
//          Jan 09,1995     RamC            Save Lsa hToken in the PCB structure
//                                          This will be closed
//                                          in ProcessLineDownWorker() routine 
//                                          to release the RAS license.

#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>     // needed for winbase.h

#include <windows.h>    // Win32 base API's
#include <stdlib.h>
#include <string.h>
#include <wchar.h>

#include <lmcons.h>
#include <raserror.h>
#include <rasman.h>
#include <rtutils.h>
#include <mprlog.h>
#include <mprerror.h>
#include <rasppp.h>
#include <pppcp.h>
#include <ppp.h>
#include <auth.h>
#include <smevents.h>
#include <smaction.h>
#include <lcp.h>
#include <timer.h>
#include <util.h>
#include <worker.h>
#define INCL_RASAUTHATTRIBUTES
#define INCL_MISC
#include <ppputil.h>

DWORD
EapGetCredentials(
        VOID *pWorkBuf,
        VOID *ppCredentials);


//**
//
// Call:        SetMsChapMppeKeys
//
// Returns:     NO_ERROR         - Success
//              Non-zero returns - Failure
//
// Description: Set the MS-CHAP-MPPE-Keys with NDISWAN
//
DWORD
SetMsChapMppeKeys(
    IN  HPORT                   hPort, 
    IN  RAS_AUTH_ATTRIBUTE *    pAttribute,
    IN  BYTE *                  pChallenge,
    IN  BYTE *                  pResponse,
    IN  DWORD                   AP,
    IN  DWORD                   APData
)
{
    RAS_COMPRESSION_INFO rciSend;
    RAS_COMPRESSION_INFO rciReceive;
    DWORD                dwRetCode      = NO_ERROR;

    ASSERT( 8 == sizeof( rciSend.RCI_LMSessionKey ) );

    ASSERT( 16 == sizeof( rciSend.RCI_UserSessionKey ) );

    //
    // Length of key is 8 (LM key) + 16 (NT key)
    //

    if ( pAttribute->dwLength < ( 6 + 8 + 16 ) )
    {
        return( ERROR_INVALID_PARAMETER );
    }

    ZeroMemory( &rciSend, sizeof( rciSend ) );
    rciSend.RCI_MacCompressionType = 0xFF;

    CopyMemory( rciSend.RCI_LMSessionKey,
                ((PBYTE)(pAttribute->Value))+6,
                8 );

    CopyMemory( rciSend.RCI_UserSessionKey,
                ((PBYTE)(pAttribute->Value))+6+8,
                16 );

    CopyMemory( rciSend.RCI_Challenge, pChallenge, 8 );

    CopyMemory( rciSend.RCI_NTResponse, pResponse, 24 );

    rciSend.RCI_Flags = CCP_SET_KEYS;

    ZeroMemory( &rciReceive, sizeof( rciReceive ) );

    rciReceive.RCI_MacCompressionType = 0xFF;

    CopyMemory( rciReceive.RCI_LMSessionKey,
                ((PBYTE)(pAttribute->Value))+6,
                8 );

    CopyMemory( rciReceive.RCI_UserSessionKey,
                ((PBYTE)(pAttribute->Value))+6+8,
                16 );

    CopyMemory( rciReceive.RCI_Challenge, pChallenge, 8 );

    CopyMemory( rciReceive.RCI_NTResponse, pResponse, 24 );

    rciReceive.RCI_Flags = CCP_SET_KEYS;

    rciSend.RCI_AuthType    = AUTH_USE_MSCHAPV2;
    rciReceive.RCI_AuthType = AUTH_USE_MSCHAPV2;

    if ( ( AP == PPP_CHAP_PROTOCOL ) &&
         ( APData == PPP_CHAP_DIGEST_MSEXT ))
    {
        rciSend.RCI_AuthType    = AUTH_USE_MSCHAPV1;
        rciReceive.RCI_AuthType = AUTH_USE_MSCHAPV1;
    }

    dwRetCode = RasCompressionSetInfo(hPort,&rciSend,&rciReceive);

    if ( dwRetCode != NO_ERROR )
    {
        PppLog( 1,"RasCompressionSetInfo failed, Error=%d", dwRetCode );
    }

    return( dwRetCode );
}

//**
//
// Call:        SetMsMppeSendRecvKeys
//
// Returns:     NO_ERROR         - Success
//              Non-zero returns - Failure
//
// Description: Set the MS-MPPE-Send-Key and MS-MPPE-Recv-Key with NDISWAN
//
DWORD
SetMsMppeSendRecvKeys(
    IN  HPORT                   hPort, 
    IN  RAS_AUTH_ATTRIBUTE *    pAttributeSendKey,
    IN  RAS_AUTH_ATTRIBUTE *    pAttributeRecvKey
)
{
    RAS_COMPRESSION_INFO rciSend;
    RAS_COMPRESSION_INFO rciRecv;
    DWORD                dwRetCode      = NO_ERROR;

    //
    // 4: for Vendor-Id.
    //
    // The Microsoft Vendor-specific RADIUS Attributes draft says that 
    // Vendor-Length should be > 4.
    //

    if ( pAttributeSendKey->dwLength <= ( 4 + 4 ) )
    {
        return( ERROR_INVALID_PARAMETER );
    }

    ZeroMemory( &rciSend, sizeof( rciSend ) );

    rciSend.RCI_MacCompressionType = 0xFF;

    rciSend.RCI_EapKeyLength = *(((BYTE*)(pAttributeSendKey->Value))+8);

    CopyMemory( rciSend.RCI_EapKey,
                ((BYTE*)(pAttributeSendKey->Value))+9,
                rciSend.RCI_EapKeyLength );

    rciSend.RCI_Flags = CCP_SET_KEYS;
    rciSend.RCI_AuthType = AUTH_USE_EAP;

    if ( pAttributeRecvKey->dwLength <= ( 4 + 4 ) )
    {
        return( ERROR_INVALID_PARAMETER );
    }

    ZeroMemory( &rciRecv, sizeof( rciRecv ) );

    rciRecv.RCI_MacCompressionType = 0xFF;

    rciRecv.RCI_EapKeyLength = *(((BYTE*)(pAttributeRecvKey->Value))+8);

    CopyMemory( rciRecv.RCI_EapKey,
                ((BYTE*)(pAttributeRecvKey->Value))+9,
                rciRecv.RCI_EapKeyLength );

    rciRecv.RCI_Flags = CCP_SET_KEYS;
    rciRecv.RCI_AuthType = AUTH_USE_EAP;

    dwRetCode = RasCompressionSetInfo(hPort,&rciSend,&rciRecv);

    if ( dwRetCode != NO_ERROR )
    {
        PppLog( 1,"RasCompressionSetInfo failed, Error=%d", dwRetCode );
    }

    return( dwRetCode );
}

//**
//
// Call:        SetUserAuthorizedAttributes
//
// Returns:     NO_ERROR         - Success
//              Non-zero returns - Failure
//
// Description:
//
DWORD
SetUserAuthorizedAttributes(
    IN  PCB *                   pPcb, 
    IN  RAS_AUTH_ATTRIBUTE *    pUserAttributes,
    IN  BOOL                    fAuthenticator,
    IN  BYTE *                  pChallenge,
    IN  BYTE *                  pResponse
)
{
    RAS_AUTH_ATTRIBUTE *    pAttribute;
    RAS_AUTH_ATTRIBUTE *    pAttributeSendKey;
    RAS_AUTH_ATTRIBUTE *    pAttributeRecvKey;
    DWORD                   dwRetCode;
    DWORD                   dwEncryptionPolicy  = 0;
    DWORD                   dwEncryptionTypes   = 0;
    BOOL                    fL2tp               = FALSE;
    BOOL                    fPptp               = FALSE;

    CreateAccountingAttributes( pPcb );

    if ( RAS_DEVICE_TYPE( pPcb->dwDeviceType ) == RDT_Tunnel_L2tp )
    {
         fL2tp = TRUE;
    }

    if ( RAS_DEVICE_TYPE( pPcb->dwDeviceType ) == RDT_Tunnel_Pptp )
    {
         fPptp = TRUE;
    }

    //
    // Find out if we are to require encrypted data using MPPE
    //

    pAttribute = RasAuthAttributeGetVendorSpecific( 311, 7, pUserAttributes );

    if ( pAttribute != NULL )
    {
        dwEncryptionPolicy
                = WireToHostFormat32(((BYTE*)(pAttribute->Value))+6);

        pAttribute = RasAuthAttributeGetVendorSpecific( 311, 
                                                        8, 
                                                        pUserAttributes );
        if ( pAttribute != NULL )
        {
            dwEncryptionTypes
                    = WireToHostFormat32(((BYTE*)(pAttribute->Value))+6);

            if ( dwEncryptionPolicy == 2 )
            {
                if (!fL2tp)
                {
                    //
                    // Find out what types of encryption are to be required
                    //

                    if (   ( dwEncryptionTypes & 0x00000002 )
                        || ( dwEncryptionTypes & 0x00000008 ) )
                    {
                        pPcb->ConfigInfo.dwConfigMask
                                            |= PPPCFG_RequireEncryption;
                        PppLog( 1,"Encryption" );
                    }

                    if ( dwEncryptionTypes & 0x00000004 )
                    {
                        pPcb->ConfigInfo.dwConfigMask 
                                            |= PPPCFG_RequireStrongEncryption;
                        PppLog( 1,"Strong encryption" );
                    }

                    if ( dwEncryptionTypes == 0 )
                    {
                        pPcb->ConfigInfo.dwConfigMask
                                            |= PPPCFG_DisableEncryption;
                        PppLog( 1,"Encryption is not allowed" );
                    }
                }
            }
            else if ( dwEncryptionPolicy == 1 )
            {
                //
                // Find out what types of encryption are to be allowed
                //

                if ( !fL2tp && !dwEncryptionTypes )
                {
                    pPcb->ConfigInfo.dwConfigMask |= PPPCFG_DisableEncryption;
                    PppLog( 1,"Encryption is not allowed" );
                }
            }
        }
    }

    //
    // Set encryption keys if we got them, provided we have not already done so
    //

    if ( !( pPcb->fFlags & PCBFLAG_MPPE_KEYS_SET ) )
    {
        pAttribute = RasAuthAttributeGetVendorSpecific(
                                311, 12, pUserAttributes);

        if ( pAttribute != NULL ) 
        {
            //
            // Set the MS-CHAP-MPPE-Keys with NDISWAN
            //

            LCPCB * pLcpCb = (LCPCB*)(pPcb->LcpCb.pWorkBuf);
            DWORD   AP;
            DWORD   APData = 0;

            AP = ( fAuthenticator ? pLcpCb->Local.Work.AP :
                                    pLcpCb->Remote.Work.AP );

            if ( AP == PPP_CHAP_PROTOCOL )
            {
                APData = ( fAuthenticator ? *(pLcpCb->Local.Work.pAPData) :
                                            *(pLcpCb->Remote.Work.pAPData) );
            }

            dwRetCode = SetMsChapMppeKeys( pPcb->hPort,
                                           pAttribute,
                                           pChallenge,
                                           pResponse,
                                           AP,
                                           APData
                                         );

            if ( NO_ERROR != dwRetCode )
            {
                return( dwRetCode );
            }

            PppLog( 1,"MS-CHAP-MPPE-Keys set" );

            pPcb->fFlags |= PCBFLAG_MPPE_KEYS_SET;
        }

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

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

            dwRetCode = SetMsMppeSendRecvKeys( pPcb->hPort,
                                               pAttributeSendKey,
                                               pAttributeRecvKey
                                             );

            if ( NO_ERROR != dwRetCode )
            {
                return( dwRetCode );
            }

            PppLog( 1,"MPPE-Send/Recv-Keys set" );

            pPcb->fFlags |= PCBFLAG_MPPE_KEYS_SET;
        }
    }

    //
    // Check if L2tp is being used
    //

    if ( fL2tp )
    {
        DWORD   dwMask          = 0;
        DWORD   dwSize          = sizeof(DWORD);
        DWORD   dwConfigMask;

        dwRetCode = RasGetPortUserData( pPcb->hPort, PORT_IPSEC_INFO_INDEX,
                        (BYTE*) &dwMask, &dwSize );

        if ( NO_ERROR != dwRetCode )
        {
            PppLog( 1, "RasGetPortUserData failed: 0x%x", dwRetCode );

            dwRetCode = NO_ERROR;
        }

        PppLog( 1, "Checking encryption. Policy=0x%x,Types=0x%x,Mask=0x%x",
            dwEncryptionPolicy, dwEncryptionTypes, dwMask );

        if ( dwMask == RASMAN_IPSEC_ESP_DES )
        {
            pPcb->pBcb->fFlags |= BCBFLAG_BASIC_ENCRYPTION;
        }
        else if ( dwMask == RASMAN_IPSEC_ESP_3_DES )
        {
            pPcb->pBcb->fFlags |= BCBFLAG_STRONGEST_ENCRYPTION;
        }

        if ( !fAuthenticator )
        {
            //
            // If the user requires maximum encryption (3DES), but we 
            // negotiated weaker encryption (56-bit DES), then return an error.
            //

            dwConfigMask = pPcb->ConfigInfo.dwConfigMask;

            if (    ( dwConfigMask & PPPCFG_RequireStrongEncryption )
                && !( dwConfigMask & PPPCFG_RequireEncryption )
                && !( dwConfigMask & PPPCFG_DisableEncryption )
                &&  ( dwMask != RASMAN_IPSEC_ESP_3_DES ) )
            {
                return( ERROR_NO_REMOTE_ENCRYPTION );
            }

            //
            // We are done with the PPPCFG_Require*Encryption flags. Let us now
            // turn them off because we don't care what kind of encryption CCP 
            // negotiates.
            //

            pPcb->ConfigInfo.dwConfigMask &= ~PPPCFG_RequireStrongEncryption;
            pPcb->ConfigInfo.dwConfigMask &= ~PPPCFG_RequireEncryption;
        }
        else if ( dwEncryptionPolicy != 0 )
        {
            BOOL    fPolicyError    = FALSE;

            //
            // There is an encryption policy
            //

            switch ( dwMask )
            {
            case 0:

                if (   ( dwEncryptionPolicy ==  2 )
                    && ( dwEncryptionTypes != 0 ) )
                {
                    fPolicyError = TRUE;
                    break;
                }

                break;

            case RASMAN_IPSEC_ESP_DES:

                if (   !( dwEncryptionTypes & 0x00000002 )
                    && !( dwEncryptionTypes & 0x00000008 ) )
                {
                    fPolicyError = TRUE;
                    break;
                }

                break;

            case RASMAN_IPSEC_ESP_3_DES:

                if (!( dwEncryptionTypes & 0x00000004 ) )
                {
                    fPolicyError = TRUE;
                    break;
                }

                break;
            }

            if ( fPolicyError )
            {
                //
                // We need to send an Accounting Stop if RADIUS sends an Access
                // Accept but we still drop the line.
                //

                pPcb->fFlags |= PCBFLAG_SERVICE_UNAVAILABLE;

                return( ERROR_NO_REMOTE_ENCRYPTION );
            }
        }
    }

    //
    // If we require encryption make sure we have the keys and that CCP is 
    // loaded.
    //

    if ( pPcb->ConfigInfo.dwConfigMask & ( PPPCFG_RequireEncryption        |
                                           PPPCFG_RequireStrongEncryption ) )
    {
        if (   !( pPcb->fFlags & PCBFLAG_MPPE_KEYS_SET )
            || ( GetCpIndexFromProtocol( PPP_CCP_PROTOCOL ) == -1 ) )
        {
            //
            // We need to send an Accounting Stop if RADIUS sends an Access
            // Accept but we still drop the line.
            //

            pPcb->fFlags |= PCBFLAG_SERVICE_UNAVAILABLE;

            return( ERROR_NO_LOCAL_ENCRYPTION );
        }
    }
    
    //
    // If we are not the authenticator then there is nothing more to set
    //

    if ( !fAuthenticator )
    {
        return( NO_ERROR );
    }
    
    //
    // Check framed protocol attribute. It must be PPP.
    // 

    pAttribute = RasAuthAttributeGet( raatFramedProtocol, pUserAttributes );

    if ( pAttribute != NULL )
    {
        if ( PtrToUlong(pAttribute->Value) != 1 )
        {
            //
            // We need to send an Accounting Stop if RADIUS sends an Access
            // Accept but we still drop the line.
            //

            pPcb->fFlags |= PCBFLAG_SERVICE_UNAVAILABLE;

            return( ERROR_UNKNOWN_FRAMED_PROTOCOL );
        }
    }
    
    //
    // Check tunnel type attribute. It must be correct.
    // 

    pAttribute = RasAuthAttributeGet( raatTunnelType, pUserAttributes );

    if ( pAttribute != NULL )
    {
        DWORD   dwTunnelType    = PtrToUlong(pAttribute->Value);

        if (   ( fL2tp && ( dwTunnelType != 3 ) )
            || ( fPptp && ( dwTunnelType != 1 ) ) )
        {
            //
            // We need to send an Accounting Stop if RADIUS sends an Access
            // Accept but we still drop the line.
            //

            pPcb->fFlags |= PCBFLAG_SERVICE_UNAVAILABLE;

            return( ERROR_WRONG_TUNNEL_TYPE );
        }
    }

    //
    // Get the logon domain attribute
    //

    pAttribute = RasAuthAttributeGetVendorSpecific( 311, 10, pUserAttributes );

    if ( pAttribute != NULL )
    {
        DWORD cbDomain = sizeof( pPcb->pBcb->szRemoteDomain ) - 1;

        if ( ( pAttribute->dwLength - 7 ) < cbDomain )
        {
            cbDomain = pAttribute->dwLength - 7;
        }
    
        ZeroMemory( pPcb->pBcb->szRemoteDomain,   
                    sizeof( pPcb->pBcb->szRemoteDomain ) );

        CopyMemory( pPcb->pBcb->szRemoteDomain, 
                    (LPSTR)((PBYTE)(pAttribute->Value)+7),
                    cbDomain );

        PppLog( 2, "Auth Attribute Domain = %s", pPcb->pBcb->szRemoteDomain);
    }

    //
    // Setup callback information, default is no callback 
    //

    pPcb->fCallbackPrivilege  = RASPRIV_NoCallback;
    pPcb->szCallbackNumber[0] = (CHAR)NULL;

    pAttribute = RasAuthAttributeGet( raatServiceType, pUserAttributes );

    if ( pAttribute != NULL ) 
    {
        if ( PtrToUlong(pAttribute->Value) == 4 )
        {
            //
            // If service type is callback framed
            //
        
            pAttribute=RasAuthAttributeGet(raatCallbackNumber,pUserAttributes);

            if ( ( pAttribute == NULL ) || ( pAttribute->dwLength == 0 ) )
            {
                pPcb->fCallbackPrivilege = RASPRIV_NoCallback |
                                           RASPRIV_CallerSetCallback;

                pPcb->szCallbackNumber[0] = (CHAR)NULL;

                PppLog(2,"Auth Attribute Caller Specifiable callback");
            }
            else
            {
                pPcb->fCallbackPrivilege = RASPRIV_AdminSetCallback;

                ZeroMemory(pPcb->szCallbackNumber, 
                           sizeof(pPcb->szCallbackNumber));

                CopyMemory( pPcb->szCallbackNumber, 
                            pAttribute->Value,
                            pAttribute->dwLength );

                PppLog( 2, "Auth Attribute Forced callback to %s",
                            pPcb->szCallbackNumber );

                //
                // Don't accept BAP Call-Requests. Otherwise, when the client 
                // calls us, we will drop the line and callback. The first call 
                // would be a waste.
                //

                pPcb->pBcb->fFlags &= ~BCBFLAG_CAN_ACCEPT_CALLS;
            }
        }
        else if ( PtrToUlong(pAttribute->Value) != 2 )
        {
            PppLog( 2, "Service Type %d is not of type Framed",
                        PtrToUlong(pAttribute->Value) );

            //
            // We need to send an Accounting Stop if RADIUS sends an Access
            // Accept but we still drop the line.
            //

            pPcb->fFlags |= PCBFLAG_SERVICE_UNAVAILABLE;

            return( ERROR_UNKNOWN_SERVICE_TYPE );
        }
    }

    if (   ( pPcb->fCallbackPrivilege & RASPRIV_CallerSetCallback )
        || ( pPcb->fCallbackPrivilege & RASPRIV_AdminSetCallback ) )
    {
        pPcb->pBcb->fFlags |= BCBFLAG_CAN_CALL;
    }

    //
    // Use idle-timeout value if we got one.
    //

    pAttribute = RasAuthAttributeGet( raatIdleTimeout, pUserAttributes );

    if ( pAttribute != NULL )
    {
        pPcb->dwAutoDisconnectTime = PtrToUlong(pAttribute->Value); 

    }
    else
    {
        pPcb->dwAutoDisconnectTime = PppConfigInfo.dwDefaulIdleTimeout;
    }

    PppLog( 2, "Auth Attribute Idle Timeout Seconds = %d",  
                pPcb->dwAutoDisconnectTime );

    //
    // Use MaxChannels value if we got one.
    //

    pAttribute = RasAuthAttributeGet( raatPortLimit, pUserAttributes );

    if ( pAttribute != NULL )
    {
        if ( PtrToUlong(pAttribute->Value) > 0 )
        {
            pPcb->pBcb->dwMaxLinksAllowed = PtrToUlong(pAttribute->Value);
        }
        else
        {
            pPcb->pBcb->dwMaxLinksAllowed =  PppConfigInfo.dwDefaultPortLimit;
        }
    }
    else
    {
        pPcb->pBcb->dwMaxLinksAllowed =  PppConfigInfo.dwDefaultPortLimit;
    }

    PppLog( 2, "AuthAttribute MaxChannelsAllowed = %d",
                pPcb->pBcb->dwMaxLinksAllowed );

    //
    // See if BAP is required
    //

    pAttribute = RasAuthAttributeGetVendorSpecific( 311, 13, pUserAttributes );

    if ( pAttribute != NULL )
    {
        if ( WireToHostFormat32( (PBYTE)(pAttribute->Value)+6 ) == 2 )
        {
            PppLog( 2, "AuthAttribute BAPRequired" ); 

            pPcb->pBcb->fFlags |= BCBFLAG_BAP_REQUIRED;
        }
    }

    //
    // For the server never send a request bring up the line
    //

    pPcb->pBcb->BapParams.dwDialExtraPercent       = 100;
    pPcb->pBcb->BapParams.dwDialExtraSampleSeconds = 100;

    pAttribute = RasAuthAttributeGetVendorSpecific( 311, 14, pUserAttributes );

    if ( ( pAttribute != NULL ) && ( pAttribute->dwLength == 10 ) )
    {
        pPcb->pBcb->BapParams.dwHangUpExtraPercent = 
              WireToHostFormat32( ((BYTE *)(pAttribute->Value))+6 );

        PppLog( 2, "AuthAttribute BAPLineDownLimit = %d",
                    pPcb->pBcb->BapParams.dwHangUpExtraPercent ); 
    }
    else
    {
        pPcb->pBcb->BapParams.dwHangUpExtraPercent =    
                                            PppConfigInfo.dwHangupExtraPercent;
    }

    pAttribute = RasAuthAttributeGetVendorSpecific( 311, 15, pUserAttributes );

    if ( ( pAttribute != NULL ) && ( pAttribute->dwLength == 10 ) )
    {
        pPcb->pBcb->BapParams.dwHangUpExtraSampleSeconds = 
              WireToHostFormat32( ((BYTE *)(pAttribute->Value))+6 );

        PppLog( 2, "AuthAttribute BAPLineDownTime = %d",
                    pPcb->pBcb->BapParams.dwHangUpExtraSampleSeconds );
    }
    else
    {
        pPcb->pBcb->BapParams.dwHangUpExtraSampleSeconds = 
                                    PppConfigInfo.dwHangUpExtraSampleSeconds;
    }

    return( NO_ERROR );
}

//**
//
// Call:        RasAuthenticateUserWorker
//
// Returns:     None.
//
// Description:
//
VOID
RasAuthenticateUserWorker(
    PVOID pContext
)
{
    PCB_WORK_ITEM * pWorkItem = (PCB_WORK_ITEM *)pContext;

    pWorkItem->PppMsg.AuthInfo.dwError = 
                    (*PppConfigInfo.RasAuthProviderAuthenticateUser)( 
                                pWorkItem->PppMsg.AuthInfo.pInAttributes,
                                &(pWorkItem->PppMsg.AuthInfo.pOutAttributes),
                                &(pWorkItem->PppMsg.AuthInfo.dwResultCode) );

    RasAuthAttributeDestroy( pWorkItem->PppMsg.AuthInfo.pInAttributes );

    InsertWorkItemInQ( pWorkItem );
}

//**
//
// Call:        RasAuthenticateClient
//
// Returns:     NO_ERROR         - Success
//              Non-zero returns - Failure
//
// Description:
//
DWORD
RasAuthenticateClient(
    IN  HPORT                   hPort,
    IN  RAS_AUTH_ATTRIBUTE *    pInAttributes
)
{
    PCB *               pPcb        = GetPCBPointerFromhPort( hPort );
    LCPCB *             pLcpCb      = NULL;
    PCB_WORK_ITEM *     pWorkItem   = NULL;
    DWORD               dwRetCode   = NO_ERROR;
    
    if ( pPcb == NULL )
    {
        RasAuthAttributeDestroy( pInAttributes );

        return( ERROR_INVALID_PORT_HANDLE );
    }

    pLcpCb = (LCPCB*)(pPcb->LcpCb.pWorkBuf);

    pWorkItem = (PCB_WORK_ITEM *)LOCAL_ALLOC( LPTR, sizeof(PCB_WORK_ITEM) );

    if ( pWorkItem == (PCB_WORK_ITEM *)NULL )
    {
        LogPPPEvent( ROUTERLOG_NOT_ENOUGH_MEMORY, GetLastError() );

        RasAuthAttributeDestroy( pInAttributes );

        return( GetLastError() );
    }

    pWorkItem->hPort                            = hPort;
    pWorkItem->dwPortId                         = pPcb->dwPortId;
    pWorkItem->Protocol                         = pLcpCb->Local.Work.AP;
    pWorkItem->PppMsg.AuthInfo.pInAttributes    = pInAttributes;
    pWorkItem->PppMsg.AuthInfo.dwId             = GetUId( pPcb, LCP_INDEX );
    pWorkItem->Process                          = ProcessAuthInfo;

    pPcb->dwOutstandingAuthRequestId = pWorkItem->PppMsg.AuthInfo.dwId;
    
    dwRetCode = RtlNtStatusToDosError( RtlQueueWorkItem( RasAuthenticateUserWorker, 
                                                         pWorkItem, 
                                                         WT_EXECUTEDEFAULT ) );
    if ( dwRetCode != NO_ERROR )
    {
        RasAuthAttributeDestroy( pInAttributes );

        LOCAL_FREE( pWorkItem );
    }

    return( dwRetCode );
}

//**
//
// Call:        RemoteError
//
// Returns:     DWORD - Remote version of this error
//
// Description: Called by a client authenticating the server.
//
DWORD
RemoteError( 
    IN DWORD dwError 
)
{
    switch( dwError )
    {
    case ERROR_NO_DIALIN_PERMISSION:
        return( ERROR_REMOTE_NO_DIALIN_PERMISSION );

    case ERROR_PASSWD_EXPIRED:
        return( ERROR_REMOTE_PASSWD_EXPIRED );

    case ERROR_ACCT_DISABLED:
        return( ERROR_REMOTE_ACCT_DISABLED );

    case ERROR_RESTRICTED_LOGON_HOURS:
        return( ERROR_REMOTE_RESTRICTED_LOGON_HOURS );

    case ERROR_AUTHENTICATION_FAILURE:
        return( ERROR_REMOTE_AUTHENTICATION_FAILURE );

    case ERROR_REQ_NOT_ACCEP:
        return( ERROR_LICENSE_QUOTA_EXCEEDED );

    default:
        return( dwError );
    }
}

//**
//
// Call:        ApIsAuthenticatorPacket
//
// Returns:     TRUE  - Packet belongs to authenticator
//              FALSE - Otherwise
//
// Description: Called to figure out whether to send the auth packet to the
//              authenticator or authenticatee.
//
BOOL
ApIsAuthenticatorPacket(
    IN DWORD         CpIndex,
    IN BYTE          bConfigCode
)
{
    switch( CpTable[CpIndex].CpInfo.Protocol )
    {
    case PPP_PAP_PROTOCOL:
        
        switch( bConfigCode )
        {
        case 1:
            return( TRUE );
        default:
            return( FALSE );
        }
        break;

    case PPP_CHAP_PROTOCOL:

        switch( bConfigCode )
        {
        case 2:
        case 5:
        case 6:
        case 7:
            return( TRUE );
        default:
            return( FALSE );
        }
        break;

    case PPP_SPAP_NEW_PROTOCOL:

        switch( bConfigCode )
        {
        case 1:
        case 6:
            return( TRUE );
        default:
            return( FALSE );
        }

        break;

    case PPP_EAP_PROTOCOL:

        switch( bConfigCode )
        {
        case 2:
            return( TRUE );
        default:
            return( FALSE );
        }

        break;

    default:
        PPP_ASSERT( FALSE );
    }

    PPP_ASSERT( FALSE );

    return( FALSE );
}

//**
//
// Call:        ApStart
//
// Returns:     TRUE  - Success
//              FALSE - Otherwise
//
// Description: Called to initiatialze the authetication protocol and to
//              initiate to authentication.
//
BOOL
ApStart(
    IN PCB * pPcb,
    IN DWORD CpIndex,
    IN BOOL  fAuthenticator
)
{
    DWORD           dwRetCode;
    PPPAP_INPUT     PppApInput;
    LCPCB *         pLcpCb      = (LCPCB*)(pPcb->LcpCb.pWorkBuf);
    CPCB *          pCpCb       = ( fAuthenticator ) 
                                    ? &(pPcb->AuthenticatorCb)
                                    : &(pPcb->AuthenticateeCb);

    pCpCb->fConfigurable = TRUE;
    pCpCb->State         = FSM_INITIAL;
    pCpCb->Protocol      = CpTable[CpIndex].CpInfo.Protocol;
    pCpCb->LastId        = (DWORD)-1;
    InitRestartCounters( pPcb, pCpCb );

    ZeroMemory( &PppApInput, sizeof( PppApInput ) );

    PppApInput.hPort                 = pPcb->hPort;
    PppApInput.fServer               = fAuthenticator;
    PppApInput.fRouter               = ( ROUTER_IF_TYPE_FULL_ROUTER == 
                                         pPcb->pBcb->InterfaceInfo.IfType );
    PppApInput.Luid                  = pPcb->Luid;
    PppApInput.dwEapTypeToBeUsed     = pPcb->dwEapTypeToBeUsed;
    PppApInput.hTokenImpersonateUser = pPcb->pBcb->hTokenImpersonateUser;
    PppApInput.pCustomAuthConnData   = pPcb->pBcb->pCustomAuthConnData;
    PppApInput.pCustomAuthUserData   = pPcb->pBcb->pCustomAuthUserData;
    PppApInput.EapUIData             = pPcb->pBcb->EapUIData;
    PppApInput.fLogon                = ( pPcb->pBcb->fFlags & 
                                         BCBFLAG_LOGON_USER_DATA );
    PppApInput.fNonInteractive       = ( pPcb->fFlags & 
                                         PCBFLAG_NON_INTERACTIVE );
    PppApInput.fConfigInfo           = pPcb->ConfigInfo.dwConfigMask;

    if ( fAuthenticator )
    {
        PppApInput.dwRetries                = pPcb->dwAuthRetries;
        PppApInput.pAPData                  = pLcpCb->Local.Work.pAPData;
        PppApInput.APDataSize               = pLcpCb->Local.Work.APDataSize;
        PppApInput.pUserAttributes          = pPcb->pUserAttributes;

        ZeroMemory( &pPcb->pBcb->szRemoteUserName, 
                    sizeof( pPcb->pBcb->szRemoteUserName ) );
    }
    else
    {
        //
        // If we are a server and we do not know who is dialing in and therefore
        // do not have credentials to use for being authenticated by the 
        // remote peer, then we wait till we do.
        //

        if ( pPcb->fFlags & PCBFLAG_IS_SERVER )
        {
            if ( strlen( pPcb->pBcb->szRemoteUserName ) == 0 )
            {
                PppLog(1,"Remote user not identifiable at this time, waiting");

                return( FALSE );
            }
        
            //
            // Ok we know who is dialed in so get credentials to used for this
            // connection.      
            //

            dwRetCode =  GetCredentialsFromInterface( pPcb );

            if ( dwRetCode != NO_ERROR )
            {
                //
                // We do not have credentials to use for this user so we
                // renegotiate LCP and do not accept the authentication option
                //

                PppLog( 1, "No credentials available to use for user=%s",
                           pPcb->pBcb->szRemoteUserName );

                FsmDown( pPcb, LCP_INDEX );

                pLcpCb->Remote.WillNegotiate &= (~LCP_N_AUTHENT); 

                FsmUp( pPcb, LCP_INDEX );

                return( FALSE );
            }
        }

        //
        // Decode the password
        //

        DecodePw( pPcb->pBcb->chSeed, pPcb->pBcb->szPassword );
        DecodePw( pPcb->pBcb->chSeed, pPcb->pBcb->szOldPassword );

        PppApInput.pszUserName          = pPcb->pBcb->szLocalUserName;
        PppApInput.pszPassword          = pPcb->pBcb->szPassword;
        PppApInput.pszDomain            = pPcb->pBcb->szLocalDomain;
        PppApInput.pszOldPassword       = pPcb->pBcb->szOldPassword;
        PppApInput.pAPData              = pLcpCb->Remote.Work.pAPData;
        PppApInput.APDataSize           = pLcpCb->Remote.Work.APDataSize;
        PppApInput.dwInitialPacketId    = (DWORD)GetUId( pPcb, CpIndex );

        if ( CpTable[CpIndex].CpInfo.Protocol == PPP_EAP_PROTOCOL )
        {
            PppApInput.fPortWillBeBundled = WillPortBeBundled( pPcb );
            PppApInput.fThisIsACallback =
                        ( pPcb->fFlags & PCBFLAG_THIS_IS_A_CALLBACK );
        }
    }

    dwRetCode = (CpTable[CpIndex].CpInfo.RasCpBegin)(&(pCpCb->pWorkBuf),
                                                     &PppApInput );

    if ( !fAuthenticator )
    {
        //
        // Encode the password back
        //

        EncodePw( pPcb->pBcb->chSeed, pPcb->pBcb->szPassword );
        EncodePw( pPcb->pBcb->chSeed, pPcb->pBcb->szOldPassword );
    }

    if ( dwRetCode != NO_ERROR )
    {
        pPcb->LcpCb.dwError = dwRetCode;

        NotifyCallerOfFailure( pPcb, dwRetCode );

        return( FALSE );
    }
    PppLog(1,"Calling APWork in APStart");
    ApWork( pPcb, CpIndex, NULL, NULL, fAuthenticator );

    return( TRUE );
}

//**
//
// Call:        ApStop
//
// Returns:     none
//
// Description: Called to stop the authentication machine.
//
VOID
ApStop(
    IN PCB *    pPcb,
    IN DWORD    CpIndex,
    IN BOOL     fAuthenticator
)
{
    CPCB * pCpCb = ( fAuthenticator )  
                        ? &(pPcb->AuthenticatorCb) 
                        : &(pPcb->AuthenticateeCb);

    if ( pCpCb->pWorkBuf == NULL )
    {
        return;
    }

    pCpCb->Protocol      = 0;
    pCpCb->fConfigurable = FALSE;
       

    if ( pCpCb->LastId != (DWORD)-1 )
    {
        RemoveFromTimerQ( 
                      pPcb->dwPortId,
                      pCpCb->LastId,
                      CpTable[CpIndex].CpInfo.Protocol,
                      fAuthenticator,
                      TIMER_EVENT_TIMEOUT );
    }

    (CpTable[CpIndex].CpInfo.RasCpEnd)( pCpCb->pWorkBuf );

    pCpCb->pWorkBuf = NULL;
}

//**
//
// Call:            ApWork
//
// Returns:         none
//
// Description: Called when and authentication packet was received or
//              a timeout occurred or to initiate authentication.
//
VOID
ApWork(
    IN PCB *         pPcb,
    IN DWORD         CpIndex,
    IN PPP_CONFIG *  pRecvConfig,
    IN PPPAP_INPUT * pApInput,
    IN BOOL          fAuthenticator
)
{
    DWORD           dwRetCode;
    DWORD           dwLength;
    PPPAP_RESULT    ApResult;
    PPP_CONFIG *    pSendConfig = (PPP_CONFIG*)(pPcb->pSendBuf->Information);
    LCPCB *         pLcpCb      =  (LCPCB*)(pPcb->LcpCb.pWorkBuf);
    CPCB *          pCpCb       = ( fAuthenticator )  
                                    ? &(pPcb->AuthenticatorCb) 
                                    : &(pPcb->AuthenticateeCb);

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

    if ( pCpCb->pWorkBuf == NULL )
    {
        if ( !ApStart( pPcb, CpIndex, fAuthenticator ) )
        {
            return;
        }
    }

    ZeroMemory( &ApResult, sizeof(ApResult) );

    dwRetCode = (CpTable[CpIndex].CpInfo.RasApMakeMessage)(
                                pCpCb->pWorkBuf,
                                pRecvConfig,
                                pSendConfig,
                                ((pLcpCb->Remote.Work.MRU > LCP_DEFAULT_MRU)
                                ? LCP_DEFAULT_MRU : pLcpCb->Remote.Work.MRU)
                                - PPP_PACKET_HDR_LEN,
                                &ApResult,
                                pApInput );

    if ( NULL != ApResult.szReplyMessage )
    {
        LocalFree( pPcb->pBcb->szReplyMessage );

        pPcb->pBcb->szReplyMessage = ApResult.szReplyMessage;
    }

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

            PppLog( 1, "Silently discarding invalid auth packet on port %d",
                    pPcb->hPort );
            break;

        default:

            pPcb->LcpCb.dwError = dwRetCode;

            PppLog( 1, "Auth Protocol %x returned error %d",
                        CpTable[CpIndex].CpInfo.Protocol, dwRetCode );

            if ( fAuthenticator )
            {
                //
                // Get the username from the CP if it supplies one.     
                //

                if ( strlen( ApResult.szUserName ) > 0 )
                {
                    strcpy( pPcb->pBcb->szRemoteUserName, ApResult.szUserName );
                }
            }

            NotifyCallerOfFailure( pPcb, dwRetCode );

            break;
        }

        return;
    }

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

    if ( ( !fAuthenticator ) && ( ApResult.fSaveUserData ) )
    {
        dwRetCode = RasSetEapUserDataA(
                        pPcb->pBcb->hTokenImpersonateUser,
                        pPcb->pBcb->szPhonebookPath,
                        pPcb->pBcb->szEntryName,
                        ApResult.pUserData,
                        ApResult.dwSizeOfUserData );

        PppLog( 2, "Saved EAP data for user, dwRetCode = %d", dwRetCode );
    }

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

    if ( ( !fAuthenticator ) && ( ApResult.fSaveConnectionData ) &&
         ( 0 != ApResult.SetCustomAuthData.dwSizeOfConnectionData ) )
    {
        NotifyCaller( pPcb, PPPMSG_SetCustomAuthData,
                      &(ApResult.SetCustomAuthData) );

        PppLog( 2, "Saved EAP data for connection" );
    }

    switch( ApResult.Action )
    {

    case APA_Send:
    case APA_SendWithTimeout:
    case APA_SendWithTimeout2:
    case APA_SendAndDone:

        HostToWireFormat16( (WORD)CpTable[CpIndex].CpInfo.Protocol,
                                        (PBYTE)(pPcb->pSendBuf->Protocol) );

        dwLength = WireToHostFormat16( pSendConfig->Length );

        LogPPPPacket(FALSE,pPcb,pPcb->pSendBuf,dwLength+PPP_PACKET_HDR_LEN);

        if ( ( dwRetCode = PortSendOrDisconnect( pPcb,
                                    (dwLength + PPP_PACKET_HDR_LEN)))
                                                != NO_ERROR )
        {
            return;
        }

        pCpCb->LastId = (DWORD)-1;

        if ( ( ApResult.Action == APA_SendWithTimeout ) ||
             ( ApResult.Action == APA_SendWithTimeout2 ) )
        {
            pCpCb->LastId = ApResult.bIdExpected;

            InsertInTimerQ( pPcb->dwPortId,
                            pPcb->hPort,
                            pCpCb->LastId,
                            CpTable[CpIndex].CpInfo.Protocol,
                            fAuthenticator,
                            TIMER_EVENT_TIMEOUT,
                            pPcb->RestartTimer );

            //
            // For SendWithTimeout2 we increment the ConfigRetryCount. This
            // means send with infinite retry count
            //

            if ( ApResult.Action == APA_SendWithTimeout2 )
            {
                (pCpCb->ConfigRetryCount)++;
            }
        }

        if ( ApResult.Action != APA_SendAndDone )
        {
            break;
        }

    case APA_Done:

        switch( ApResult.dwError )
        {
        case NO_ERROR:

            //
            // If authentication was successful
            //

            if ( CpTable[CpIndex].CpInfo.Protocol == PPP_EAP_PROTOCOL )
            {
                if ( fAuthenticator )
                {
                    pPcb->dwServerEapTypeId = ApResult.dwEapTypeId;
                }
                else
                {
                    VOID *pCredentials = NULL;

                    pPcb->dwClientEapTypeId = ApResult.dwEapTypeId;

                    //
                    // Call the eap dll to collect credentials here
                    // so that they can be passed to rasman to be
                    // saved in the cred manager.
                    //
                    if(     (NO_ERROR == EapGetCredentials(pCpCb->pWorkBuf,
                                                          &pCredentials))
                        &&  (NULL != pCredentials))
                    {
                        //
                        // Below call is not fatal.
                        //
                        (void) RasSetPortUserData(
                                    pPcb->hPort,
                                    PORT_CREDENTIALS_INDEX,
                                    pCredentials,
                                    sizeof(RASMAN_CREDENTIALS));

                        ZeroMemory(pCredentials,
                                        sizeof(RASMAN_CREDENTIALS));
                        LocalFree(pCredentials);
                    }
                }
            }

            if ( fAuthenticator )
            {
                RAS_AUTH_ATTRIBUTE * pAttribute;
                RAS_AUTH_ATTRIBUTE * pUserAttributes = NULL;

                if ( NULL != pPcb->pBcb->szRemoteIdentity )
                {
                    LOCAL_FREE( pPcb->pBcb->szRemoteIdentity );

                    pPcb->pBcb->szRemoteIdentity = NULL;
                }

                pPcb->pBcb->szRemoteIdentity =
                    LOCAL_ALLOC( LPTR, strlen( ApResult.szUserName ) + 1 );

                if ( NULL == pPcb->pBcb->szRemoteIdentity )
                {
                    dwRetCode = GetLastError();

                    pPcb->LcpCb.dwError = dwRetCode;

                    NotifyCallerOfFailure( pPcb, dwRetCode );

                    return;
                }

                strcpy( pPcb->pBcb->szRemoteIdentity, ApResult.szUserName );

                dwRetCode = ExtractUsernameAndDomain( 
                                          ApResult.szUserName,
                                          pPcb->pBcb->szRemoteUserName, 
                                          NULL );

                if ( dwRetCode != NO_ERROR )
                {
                    pPcb->LcpCb.dwError = dwRetCode;

                    NotifyCallerOfFailure( pPcb, dwRetCode );

                    return;
                }

                if ( 0 == pPcb->pBcb->szLocalUserName[0] )
                {
                    if ( NO_ERROR != GetCredentialsFromInterface( pPcb ) )
                    {
                        pPcb->pBcb->szLocalUserName[0] = 
                        pPcb->pBcb->szPassword[0]      = 
                        pPcb->pBcb->szLocalDomain[0]   = 0;
                    }
                }

                if ( ApResult.pUserAttributes != NULL )
                {
                    pPcb->pAuthProtocolAttributes = ApResult.pUserAttributes;
                    
                    pUserAttributes = ApResult.pUserAttributes;
                }
                else
                {
                    pUserAttributes = pPcb->pAuthenticatorAttributes;
                }

                //
                // Set all the user connection parameters authorized by the
                // back-end authenticator
                //

                dwRetCode = SetUserAuthorizedAttributes(
                                                pPcb, 
                                                pUserAttributes,
                                                fAuthenticator,
                                                (BYTE*)&(ApResult.abChallenge),
                                                (BYTE*)&(ApResult.abResponse));

                if ( dwRetCode != NO_ERROR )
                {
                    pPcb->LcpCb.dwError = dwRetCode;

                    NotifyCallerOfFailure( pPcb, dwRetCode );

                    return;
                }

                //
                // If we are a server and we negotiated to be authenticated 
                // by the remote peer we can do so now that we know who 
                // is dialed in.
                //

                if ( ( pLcpCb->Remote.Work.AP != 0 )            &&
                     ( pPcb->AuthenticateeCb.pWorkBuf == NULL ) &&
                     ( pPcb->AuthenticateeCb.fConfigurable )    &&
                     ( pPcb->fFlags & PCBFLAG_IS_SERVER  ) )
                {
                    CpIndex = GetCpIndexFromProtocol( pLcpCb->Remote.Work.AP );

                    PPP_ASSERT(( CpIndex != (DWORD)-1 ));

                    if ( !ApStart( pPcb, CpIndex, FALSE ) )
                    {
                        return;
                    }
                }
            }
            else
            {
                //
                // Get the username from the CP if it supplies one.     
                //

                if (   ( strlen( pPcb->pBcb->szLocalUserName ) == 0 )
                    && ( strlen( ApResult.szUserName ) > 0 ) )
                {
                    strcpy( pPcb->pBcb->szLocalUserName, ApResult.szUserName );
                }

                dwRetCode = SetUserAuthorizedAttributes(
                                            pPcb, 
                                            ApResult.pUserAttributes,
                                            fAuthenticator,
                                            (BYTE*)&(ApResult.abChallenge),
                                            (BYTE*)&(ApResult.abResponse));

                if ( dwRetCode != NO_ERROR )
                {
                    pPcb->LcpCb.dwError = dwRetCode;

                    NotifyCallerOfFailure( pPcb, dwRetCode );

                    return;
                }

                pPcb->pAuthProtocolAttributes = ApResult.pUserAttributes;
            }

            pCpCb->State = FSM_OPENED;

            FsmThisLayerUp( pPcb, CpIndex );

            break;

        case ERROR_PASSWD_EXPIRED:

            if ( pPcb->fFlags & PCBFLAG_IS_SERVER )
            {
                //
                // We are a server and hence in non-interactive mode and
                // hence we cannot do this.
                //

                pPcb->LcpCb.dwError = ApResult.dwError;

                NotifyCallerOfFailure( pPcb, pPcb->LcpCb.dwError );

                return;
            }
            else
            {
                //
                // Password has expired so the user has to change his/her
                // password.
                //

                NotifyCaller( pPcb, PPPMSG_ChangePwRequest, NULL );
            }
                
            break;

        default:

            //
            // If we can retry with a new password then tell the client to
            // get a new one from the user.
            //

            if ( (!fAuthenticator) && ( ApResult.fRetry ))
            {
                if ( pPcb->fFlags & PCBFLAG_IS_SERVER )
                {
                    //
                    // We are a server and hence in non-interactive mode and
                    // hence we cannot do this.
                    //

                    pPcb->LcpCb.dwError = ApResult.dwError;

                    NotifyCallerOfFailure( pPcb, pPcb->LcpCb.dwError );

                    return;
                }
                else
                {
                    PppLog( 2, "Sending auth retry message to UI" );

                    NotifyCaller( pPcb, PPPMSG_AuthRetry, &(ApResult.dwError) );
                }
            }
            else
            {
                PppLog( 1, "Auth Protocol %x terminated with error %d",
                           CpTable[CpIndex].CpInfo.Protocol, ApResult.dwError );

                if ( ApResult.szUserName[0] != (CHAR)NULL )
                {
                    strcpy( pPcb->pBcb->szRemoteUserName, ApResult.szUserName );
                }

                if ( !( pPcb->fFlags & PCBFLAG_IS_SERVER ) && 
                      ( fAuthenticator) )
                {
                    pPcb->LcpCb.dwError = RemoteError( ApResult.dwError );
                }
                else
                {
                    pPcb->LcpCb.dwError = ApResult.dwError;
                }

                NotifyCallerOfFailure( pPcb, pPcb->LcpCb.dwError );

                return;
            }

            break;
        }

        break;

    case APA_Authenticate:

        if ( fAuthenticator )
        {
            DWORD                dwIndex                = 0;
            DWORD                dwExtraAttributes      = 0;
            DWORD                dwNumUserAttributes    = 0;
            RAS_AUTH_ATTRIBUTE * pUserAttributes        = NULL;

            for ( dwNumUserAttributes = 0; 
                  pPcb->pUserAttributes[dwNumUserAttributes].raaType 
                                                                != raatMinimum;
                  dwNumUserAttributes++ );

            if ( CpTable[CpIndex].CpInfo.Protocol == PPP_EAP_PROTOCOL )
            {
                //
                // One more for Framed-MTU
                //

                dwExtraAttributes = 1;
            }

            pUserAttributes = RasAuthAttributeCopyWithAlloc(
                                    ApResult.pUserAttributes,
                                    dwNumUserAttributes + dwExtraAttributes );

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

                pPcb->LcpCb.dwError = dwRetCode;

                NotifyCallerOfFailure( pPcb, pPcb->LcpCb.dwError );

                return;
            }

            if ( dwExtraAttributes )
            {
                ULONG mru = (pLcpCb->Remote.Work.MRU > LCP_DEFAULT_MRU) ?
                            LCP_DEFAULT_MRU : pLcpCb->Remote.Work.MRU;
                //
                // Insert the Framed-MTU attribute at the start.
                //

                dwRetCode = RasAuthAttributeInsert( 
                                0,
                                pUserAttributes,
                                raatFramedMTU,
                                FALSE,
                                4,
                                (LPVOID) 
                                ( UlongToPtr(mru)) );

                if ( dwRetCode != NO_ERROR )
                {
                    RasAuthAttributeDestroy( pUserAttributes );

                    pPcb->LcpCb.dwError = dwRetCode;

                    NotifyCallerOfFailure( pPcb, pPcb->LcpCb.dwError );

                    return;
                }
            }

            //
            // Insert the extra (user) attributes at the start. Attributes
            // returned by the auth protocol follow.
            //

            for ( dwIndex = 0; dwIndex < dwNumUserAttributes; dwIndex++ )
            {
                dwRetCode = RasAuthAttributeInsert( 
                                dwIndex + dwExtraAttributes,
                                pUserAttributes,
                                pPcb->pUserAttributes[dwIndex].raaType,
                                FALSE,
                                pPcb->pUserAttributes[dwIndex].dwLength,
                                pPcb->pUserAttributes[dwIndex].Value );

                if ( dwRetCode != NO_ERROR )
                {
                    RasAuthAttributeDestroy( pUserAttributes );

                    pPcb->LcpCb.dwError = dwRetCode;

                    NotifyCallerOfFailure( pPcb, pPcb->LcpCb.dwError );

                    return;
                }
            }

            dwRetCode = RasAuthenticateClient( pPcb->hPort, pUserAttributes );

            if ( dwRetCode != NO_ERROR )
            {
                pPcb->LcpCb.dwError = dwRetCode;

                NotifyCallerOfFailure( pPcb, pPcb->LcpCb.dwError );

                return;
            }
        }
        
        break;

    case APA_NoAction:

        break;

    default:

        break;
    }

    //
    // Check to see if we have to bring up the UI for EAP
    //

    if ( ( !fAuthenticator ) && ( ApResult.fInvokeEapUI ) )
    {
        NotifyCaller(pPcb, PPPMSG_InvokeEapUI, &(ApResult.InvokeEapUIData));
    }
}