1881 lines
58 KiB
C++
1881 lines
58 KiB
C++
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
|
|
|
|
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
|
|
|
|
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
|
|
|
|
// PARTICULAR PURPOSE.
|
|
|
|
//
|
|
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
|
|
#include <stdio.h>
|
|
#include "EapHostSupplicant.h"
|
|
#include "memory.h"
|
|
#include "EapHostCommon.h"
|
|
#include "resource.h"
|
|
#include "msxml6.h"
|
|
|
|
using namespace SDK_METHOD_SAMPLE_COMMON;
|
|
|
|
#define CONNECTION_PROPERTIES 1
|
|
#define USER_PROPERTIES 2
|
|
|
|
/// Store the SessionId for the entire Authentication Session.
|
|
EAP_SESSIONID eapSessionId = 0;
|
|
/// Handle to the library that implements EapHost Peer APIs.
|
|
HMODULE handleLibrary = NULL;
|
|
/// Stores the current action that suppliacnt has to take.
|
|
EapHostPeerResponseAction action = EapHostPeerResponseNone;
|
|
|
|
/// GUID that supplicant-eaphost use for NAP Callback function.
|
|
const GUID connectionId = {0x7128fce8,0x3ca0,0x4708,{0x9c, 0x8a, 0xad, 0x76, 0xc1, 0xb2, 0xfc, 0x53}};
|
|
|
|
/// Stores the connection data.
|
|
BYTE *connectionData = NULL;
|
|
/// Stores the size of the connection data.
|
|
DWORD sizeConnectionData = 0;
|
|
/// Stores the user data.
|
|
BYTE *userData = NULL;
|
|
/// Stores the size of the user data.
|
|
DWORD sizeUserData = 0;
|
|
|
|
/// The execution of runtime APIs and UI APIs should be processed by two different threads.
|
|
|
|
/// The event used by Runtime thread to wait for a signal from UI thread.
|
|
HANDLE event_WaitforUIThread = NULL;
|
|
|
|
/// The event used by UI thread to wait for a signal from Runtime thread.
|
|
HANDLE event_WaitforRuntimeThread = NULL;
|
|
|
|
/// Handle to a thread that handles UI calls (API - EapHostPeerInvokeInteractiveUI) to EapHost.
|
|
HANDLE UIThreadHandle = NULL;
|
|
|
|
/// Data that is shared between the Runtime thread and UI thread.
|
|
DWORD dwSizeofUIContextData = 0;
|
|
BYTE* pUIContextData = NULL;
|
|
DWORD dwSizeOfDataFromInteractiveUI = 0;
|
|
BYTE* pDataFromInteractiveUI = NULL;
|
|
EAP_METHOD_TYPE eapType = {0};
|
|
|
|
/// Stores the most recent packet received from Authenticator.
|
|
BYTE *pReceivedPacket = NULL;
|
|
/// Stores the size of the most recent packet received from Authenticator.
|
|
DWORD gdwReceivedPacketLen = 0;
|
|
|
|
/// Handle to an event that is used to signal between the receiving thread and
|
|
/// authentication thread.
|
|
HANDLE event_ReceivePacket = NULL;
|
|
BOOL needToWaitForEvent = TRUE;
|
|
|
|
/// Global variables are defined in supplicantMain.cpp
|
|
extern BYTE *g_pIdentityRequest;
|
|
extern DWORD g_dwIdentityRequestPacketLen;
|
|
|
|
//
|
|
// Declaration to EAP Host Peer APIs function pointers.
|
|
//
|
|
EapHostPeerInitialize funcEapHostPeerInitialize = NULL;
|
|
EapHostPeerUninitialize funcEapHostPeerUninitialize = NULL;
|
|
EapHostPeerBeginSession funcBeginSession = NULL;
|
|
EapHostPeerProcessReceivedPacket funcProcessReceivedPacket = NULL;
|
|
EapHostPeerGetSendPacket funcGetSendPacket = NULL;
|
|
EapHostPeerGetResult funcGetResult = NULL;
|
|
EapHostPeerGetUIContext funcGetUIContext = NULL;
|
|
EapHostPeerSetUIContext funcSetUIContext = NULL;
|
|
EapHostPeerGetResponseAttributes funcGetRespAttributes = NULL;
|
|
EapHostPeerSetResponseAttributes funcSetRespAttributes = NULL;
|
|
EapHostPeerGetAuthStatus funcGetAuthStatus = NULL;
|
|
EapHostPeerEndSession funcEndSession = NULL;
|
|
EapHostPeerClearConnection funcClearConnection = NULL;
|
|
EapHostPeerFreeEapError funcFreeEapError = NULL;
|
|
|
|
//
|
|
// Demonstrate how to get function pointers to EAP Host Peer APIs.
|
|
//
|
|
DWORD GetEapHostPeerAPIsFunctionPointers()
|
|
{
|
|
DWORD retCode = NO_ERROR;
|
|
|
|
//
|
|
//Load the library -- eappprxy.dll that implemenst the Peer Side EapHost Runtime APIs.
|
|
//
|
|
handleLibrary = LoadLibrary(L"eappprxy.dll");
|
|
if (handleLibrary == NULL)
|
|
{
|
|
retCode = GetLastError();
|
|
EapTrace("Loading eappprxy.dll failed: %d\n", retCode);
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
//Get the function pointer to all the functions implemented inside the dll.
|
|
//
|
|
funcEapHostPeerInitialize = (EapHostPeerInitialize)
|
|
GetProcAddress(handleLibrary, (LPCSTR)"EapHostPeerInitialize");
|
|
funcEapHostPeerUninitialize = (EapHostPeerUninitialize)
|
|
GetProcAddress(handleLibrary, (LPCSTR)"EapHostPeerUninitialize");
|
|
funcBeginSession = (EapHostPeerBeginSession)
|
|
GetProcAddress(handleLibrary, (LPCSTR)"EapHostPeerBeginSession");
|
|
funcProcessReceivedPacket = (EapHostPeerProcessReceivedPacket)
|
|
GetProcAddress(handleLibrary, (LPCSTR)"EapHostPeerProcessReceivedPacket");
|
|
funcGetSendPacket = (EapHostPeerGetSendPacket)
|
|
GetProcAddress(handleLibrary, (LPCSTR)"EapHostPeerGetSendPacket");
|
|
funcGetResult = (EapHostPeerGetResult)
|
|
GetProcAddress(handleLibrary, (LPCSTR)"EapHostPeerGetResult");
|
|
funcGetUIContext = (EapHostPeerGetUIContext)
|
|
GetProcAddress(handleLibrary, (LPCSTR)"EapHostPeerGetUIContext");
|
|
funcSetUIContext = (EapHostPeerSetUIContext)
|
|
GetProcAddress(handleLibrary, (LPCSTR)"EapHostPeerSetUIContext");
|
|
funcGetRespAttributes = (EapHostPeerGetResponseAttributes)
|
|
GetProcAddress(handleLibrary, (LPCSTR)"EapHostPeerGetResponseAttributes");
|
|
funcSetRespAttributes = (EapHostPeerSetResponseAttributes)
|
|
GetProcAddress(handleLibrary, (LPCSTR)"EapHostPeerSetResponseAttributes");
|
|
funcGetAuthStatus = (EapHostPeerGetAuthStatus)
|
|
GetProcAddress(handleLibrary, (LPCSTR)"EapHostPeerGetAuthStatus");
|
|
funcEndSession = (EapHostPeerEndSession)
|
|
GetProcAddress(handleLibrary, (LPCSTR)"EapHostPeerEndSession");
|
|
funcClearConnection = (EapHostPeerClearConnection)
|
|
GetProcAddress(handleLibrary, (LPCSTR)"EapHostPeerClearConnection");
|
|
funcFreeEapError = (EapHostPeerFreeEapError)
|
|
GetProcAddress(handleLibrary, (LPCSTR)"EapHostPeerFreeEapError");
|
|
|
|
if (funcBeginSession == NULL ||
|
|
funcProcessReceivedPacket == NULL ||
|
|
funcGetSendPacket == NULL ||
|
|
funcGetResult == NULL ||
|
|
funcGetUIContext == NULL ||
|
|
funcSetUIContext == NULL ||
|
|
funcGetRespAttributes == NULL ||
|
|
funcSetRespAttributes == NULL ||
|
|
funcGetAuthStatus == NULL ||
|
|
funcEndSession == NULL ||
|
|
funcClearConnection == NULL ||
|
|
funcFreeEapError == NULL
|
|
)
|
|
{
|
|
retCode = HRESULT_CODE(E_POINTER);
|
|
}
|
|
|
|
Cleanup:
|
|
return retCode;
|
|
}
|
|
|
|
//
|
|
// Demonstrate usage of EAPHostGetMethods, showing supplicant-specific
|
|
// method selection and then using any of the given installed EAP Method
|
|
// depending on what properties are supported by that EAP Method.
|
|
//
|
|
DWORD GetEapMethodFromListOfInstalledEapMethods(EAP_METHOD_TYPE *pEapMethodType)
|
|
{
|
|
DWORD retCode = ERROR_SUCCESS;
|
|
EAP_METHOD_INFO_ARRAY eapMethodsInfo = {0};
|
|
EAP_ERROR *pEapError = NULL;
|
|
HWND hwndParent = {0};
|
|
|
|
// Sanity Check
|
|
if(!pEapMethodType)
|
|
{
|
|
//Report Error
|
|
retCode = ERROR_INVALID_PARAMETER;
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Call the EapHost Config API to get the list of Eap Methods installed and the properties each
|
|
// one supports.
|
|
retCode = EapHostPeerGetMethods(&eapMethodsInfo,
|
|
&pEapError);
|
|
if(retCode != ERROR_SUCCESS)
|
|
{
|
|
//Report Error
|
|
EapHostPeerFreeErrorMemory(pEapError);
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Raises a GUI to display the information that is received from the EapHostPeerGetMethods API.
|
|
// One can also configure EapMethod from the GUI.
|
|
retCode = DisplayInstalledEapMethods(hwndParent, &eapMethodsInfo);
|
|
if(retCode != ERROR_SUCCESS)
|
|
{
|
|
//Report Error
|
|
}
|
|
|
|
//
|
|
// For demonstration purpose, we select that we will be using EapMethod - EAPTYPE for authentication.
|
|
//
|
|
pEapMethodType->eapType.type = EAPTYPE;
|
|
pEapMethodType->eapType.dwVendorId = 0;
|
|
pEapMethodType->eapType.dwVendorType = 0;
|
|
pEapMethodType->dwAuthorId = AUTHOR_ID;
|
|
|
|
//
|
|
// Free the Memory that EapHost has allocated.
|
|
//
|
|
EapHostPeerFreeMemory((BYTE *)eapMethodsInfo.pEapMethods);
|
|
|
|
Cleanup:
|
|
return retCode;
|
|
}
|
|
|
|
|
|
//
|
|
// Demonstrate how does supplicant find out from EAPHost that
|
|
// it needs to re-authenticate when the health state changes.
|
|
//
|
|
void SdkEapNotificationHandler(
|
|
IN GUID connId,
|
|
IN void* pContextData
|
|
)
|
|
{
|
|
DWORD retCode = ERROR_SUCCESS;
|
|
|
|
//
|
|
// Verify if the input parameters --- connectionId and ContextData
|
|
// are the same as passed to the BeginSession API.
|
|
//
|
|
UNREFERENCED_PARAMETER(connId);
|
|
UNREFERENCED_PARAMETER(pContextData);
|
|
|
|
//
|
|
// Check if any active authentication session is in progress.
|
|
// If yes, we need to end the active session and start a fresh authentication session.
|
|
// In this Sample code, we assume that the global variable "eapSessionId" is set
|
|
// when an active authentication is taking place and is reset to NULL when authentication
|
|
// complete.
|
|
//
|
|
if(eapSessionId) // An Authentication Session is in Progress.
|
|
{
|
|
// End the current authentication session.
|
|
EAP_ERROR *pEapError = NULL;
|
|
retCode = funcEndSession(eapSessionId, &pEapError);
|
|
if(retCode != ERROR_SUCCESS)
|
|
{
|
|
//Report Error
|
|
funcFreeEapError(pEapError);
|
|
return;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Re-Auth since there is a change in the health state of the supplicant.
|
|
//
|
|
retCode = BeginAuthentication();
|
|
if(retCode != ERROR_SUCCESS)
|
|
{
|
|
//Report Error
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Initialize the resources used for authentication.
|
|
//
|
|
DWORD Initialize()
|
|
{
|
|
DWORD retCode = ERROR_SUCCESS;
|
|
|
|
//
|
|
// Get function pointers to EAP Host Peer APIs
|
|
//
|
|
retCode = GetEapHostPeerAPIsFunctionPointers();
|
|
if(retCode != ERROR_SUCCESS)
|
|
{
|
|
//Report Error
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
//Create an event which Runtime thread uses to listen for UI thread.
|
|
//
|
|
event_WaitforUIThread = CreateEvent(NULL, // no security attributes
|
|
FALSE, // auto-reset enabled
|
|
FALSE, // non-signaled state initially
|
|
NULL); // event without a name
|
|
if(event_WaitforUIThread == NULL)
|
|
{
|
|
// Report Error
|
|
retCode = GetLastError();
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Create an event which UI thread uses to listen to Runtime thread.
|
|
//
|
|
event_WaitforRuntimeThread = CreateEvent(NULL, //no security attributes
|
|
FALSE, // auto-reset enabled
|
|
FALSE, // non-signaled state initially
|
|
NULL); // event without a name
|
|
if(event_WaitforRuntimeThread == NULL)
|
|
{
|
|
// Report Error
|
|
retCode = GetLastError();
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Create a thread that handles UI calls (API - EapHostPeerInvokeInteractiveUI) to EapHost.
|
|
//
|
|
UIThreadHandle = (HANDLE) CreateThread(
|
|
NULL,
|
|
0,
|
|
(LPTHREAD_START_ROUTINE)HandleUICalls,
|
|
0,
|
|
0,
|
|
NULL);
|
|
if(UIThreadHandle == NULL)
|
|
{
|
|
retCode = GetLastError();
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// The following comment is just a suggestion.
|
|
// 1. Create a thread (Listener thread) with function "ReceiveEapPackets" to be
|
|
// executed by the thread which would listen for EapData from Authenticator.
|
|
// Ofcourse, EapData will be contained in 802.1x or PPP packet.
|
|
// 2. Create an event "event_ReceivePacket" that listener thread will fire when it
|
|
// receives a EapData. The authenticating thread will pass the data to EapHost
|
|
// to process through the API -- EapHostPeerProcessReceivedPacket().
|
|
|
|
Cleanup:
|
|
return retCode;
|
|
}
|
|
|
|
|
|
//
|
|
// Demonstrate the handling of EapHostPeerResponseAction returned from EAP Host.
|
|
//
|
|
DWORD HandleEapHostPeerResponseAction(EapHostPeerResponseAction responseAction)
|
|
{
|
|
DWORD retCode = ERROR_SUCCESS;
|
|
|
|
switch(responseAction)
|
|
{
|
|
//
|
|
// Suggests that authentication result packet has come from the authenticator.
|
|
//
|
|
case EapHostPeerResponseResult:
|
|
retCode = HandleEapHostPeerResponseAction_GetResult(EapHostPeerMethodResultFromMethod);
|
|
|
|
break;
|
|
|
|
//
|
|
// Suggests that UI needs to be invoked.
|
|
//
|
|
case EapHostPeerResponseInvokeUi:
|
|
retCode = HandleEapHostPeerResponseAction_InvokeUI();
|
|
|
|
break;
|
|
|
|
//
|
|
// Suggests that there are EapAttributes with EapHost/EapMethod that needs to be processed by supplicant.
|
|
//
|
|
case EapHostPeerResponseRespond:
|
|
retCode = HandleEapHostPeerResponseAction_Respond();
|
|
|
|
break;
|
|
|
|
//
|
|
// Suggests that some internal problem occured which cannot be fixed. Therefore close the current
|
|
// authentication session and begin authentication from start.
|
|
//
|
|
case EapHostPeerResponseStartAuthentication:
|
|
{
|
|
//End the existing Session.
|
|
EAP_ERROR *pEapError = NULL;
|
|
retCode = funcEndSession(eapSessionId, &pEapError);
|
|
if(retCode != ERROR_SUCCESS)
|
|
{
|
|
//Report Error
|
|
funcFreeEapError(pEapError);
|
|
goto Cleanup;
|
|
}
|
|
|
|
//Start a new Session.
|
|
retCode = BeginAuthentication();
|
|
if(retCode != ERROR_SUCCESS)
|
|
{
|
|
//Report Error
|
|
goto Cleanup;
|
|
}
|
|
|
|
}
|
|
break;
|
|
|
|
//
|
|
// Suggests that there is a packet that EapMethod wants to send to Authenticator. Therefore,
|
|
// supplicant should get the packet from EapMethod via EapHost and transport it to the authenticator.
|
|
//
|
|
case EapHostPeerResponseSend:
|
|
retCode = HandleEapHostPeerResponseAction_Send();
|
|
|
|
break;
|
|
|
|
//
|
|
// No action needs to be taken.
|
|
//
|
|
case EapHostPeerResponseNone:
|
|
|
|
break;
|
|
|
|
//
|
|
// Suggests that no action needs to be taken.
|
|
//
|
|
case EapHostPeerResponseDiscard:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
}
|
|
|
|
if(retCode != ERROR_SUCCESS)
|
|
{
|
|
// Report Error
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// If action is ResponseResult, and after we process the result, we are done and
|
|
// should end the authentication session.
|
|
//
|
|
if(responseAction == EapHostPeerResponseResult)
|
|
{
|
|
EAP_ERROR *pEapError = NULL;
|
|
retCode = funcEndSession(eapSessionId, &pEapError);
|
|
if(retCode != ERROR_SUCCESS)
|
|
{
|
|
//Report Error
|
|
funcFreeEapError(pEapError);
|
|
goto Cleanup;
|
|
}
|
|
//
|
|
// Listener thread should no longer expect more packet.
|
|
//
|
|
needToWaitForEvent = FALSE;
|
|
}
|
|
|
|
Cleanup:
|
|
return retCode;
|
|
}
|
|
|
|
|
|
//
|
|
// Demonstrate the sequence of entire EAP Authentication Process.
|
|
//
|
|
DWORD BeginAuthentication()
|
|
{
|
|
DWORD retCode = ERROR_SUCCESS;
|
|
HRESULT hr = S_OK;
|
|
DWORD dwFlags = 0;
|
|
size_t threadId = 0;
|
|
EAP_ERROR *pEapError = NULL;
|
|
HANDLE hTokenImpersonateUser = NULL;
|
|
|
|
//Connection Data obtained from EapHost
|
|
IXMLDOMDocument2 *pXMLConnectionProperties = NULL;
|
|
BYTE *pConnectionDataOut = NULL;
|
|
DWORD dwSizeOfConnectionDataOut = 0;
|
|
|
|
//
|
|
// Wait till the UI thread to complete the configuration.
|
|
//
|
|
retCode = WaitForSingleObject(event_WaitforUIThread, INFINITE);
|
|
if(retCode != WAIT_OBJECT_0) /// WAIT_FAILED, WAIT_ABANDONED, WAIT_TIMEOUT all error.
|
|
{
|
|
//Report Error.
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// There are two main ways to generate Config Blob for authentication.
|
|
// One is through UI configuration.
|
|
// Other one is using XML files and XML APIs of EapHost to generate Config Blob.
|
|
// Below is demosntration of how to do that using XML files/APIs.
|
|
//
|
|
|
|
//
|
|
// Get the Config Data and User Data from registry.
|
|
// Note: Registry is just an example of a place where to store such data!!!
|
|
// If this is the first time user is authenticating, no such registry will be present.
|
|
// These data are present in the registry if the EAP method wishes to save them for
|
|
// future authentications.
|
|
//
|
|
retCode = ReadBLOBFromRegistry(CONNECTION_PROPERTIES,
|
|
sizeConnectionData,
|
|
connectionData);
|
|
if((retCode != ERROR_SUCCESS) && (retCode != ERROR_FILE_NOT_FOUND))
|
|
{
|
|
EapTrace("ReadBLOBFromRegistry returned error = %d",retCode);
|
|
goto Cleanup;
|
|
}
|
|
|
|
if(retCode == ERROR_FILE_NOT_FOUND)
|
|
{
|
|
//
|
|
// No registry present that stores Config Data.
|
|
// Most common scenario -- Administrator pushes Config Data in XML file format
|
|
// through group policy to each machines. These XML files are stored at some location
|
|
// or plumbed to some registry.
|
|
//
|
|
// Here we assume that the Config XML file is stored on hard drive.
|
|
//
|
|
|
|
//
|
|
// Demonstrates how to get Config blob from Config XML file using EAPHost APIs.
|
|
//
|
|
|
|
// Convert XML File to XMLDomDocument
|
|
retCode = CreateDOMDocumentFromXML(CONNECTION_PROPERTIES, &pXMLConnectionProperties);
|
|
if(retCode != ERROR_SUCCESS)
|
|
{
|
|
//Report Error
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Get XMLDOMElement from XMLDOMDocument.
|
|
IXMLDOMElement *pEapConnNode;
|
|
hr = pXMLConnectionProperties->get_documentElement(&pEapConnNode);
|
|
|
|
// Call EapHost Config API to convert XMLDomElement to Config BLOB
|
|
retCode = EapHostPeerConfigXml2Blob(
|
|
dwFlags,
|
|
pEapConnNode,
|
|
&dwSizeOfConnectionDataOut,
|
|
&pConnectionDataOut,
|
|
&eapType,
|
|
&pEapError
|
|
);
|
|
if(retCode != ERROR_SUCCESS)
|
|
{
|
|
//Report Error
|
|
EapHostPeerFreeErrorMemory(pEapError);
|
|
goto Cleanup;
|
|
}
|
|
|
|
if(pConnectionDataOut)
|
|
{
|
|
//
|
|
// Copy it to the global variable connectiondata.
|
|
//
|
|
sizeConnectionData = dwSizeOfConnectionDataOut;
|
|
retCode = AllocateMemory(sizeConnectionData, (PVOID*)&connectionData);
|
|
if (retCode != NO_ERROR)
|
|
goto Cleanup;
|
|
CopyMemory(connectionData, pConnectionDataOut, dwSizeOfConnectionDataOut);
|
|
|
|
//
|
|
// Finally free the ConnectionData Buffer obtained from EapHost.
|
|
//
|
|
EapHostPeerFreeMemory(pConnectionDataOut);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Check to see if user blob is saved from previous authentication which can be used
|
|
// for this authentication.
|
|
//
|
|
retCode = ReadBLOBFromRegistry(USER_PROPERTIES,
|
|
sizeUserData,
|
|
userData);
|
|
if((retCode != ERROR_SUCCESS) && (retCode != ERROR_FILE_NOT_FOUND))
|
|
{
|
|
EapTrace("ReadBLOBFromRegistry returned error = %d",retCode);
|
|
goto Cleanup;
|
|
}
|
|
|
|
if(retCode == ERROR_FILE_NOT_FOUND)
|
|
{
|
|
//
|
|
// In certain cases like Guest Access at some location (e.g. an airport), one may
|
|
// receive an user XML file to use for authentication.
|
|
// If such is the case, one needs to follow the same sequence demonstrated above
|
|
// for getting the ConfigBlob from Config XML file.
|
|
// So if required to generate UserBlob from User XML file ---
|
|
// 1. Create a IXMLDOMElement from User XML file.
|
|
// 2. Call the EapHost API -- EapHostPeerCredentailsXml2Blob that returns UserBlob
|
|
// 3. Save it and finally call EapHostPeerFreeMemory when done with the user blob.
|
|
//
|
|
}
|
|
|
|
|
|
//
|
|
// This is just a demonstration of how to create an Impersonation Token.
|
|
// Required by methods to access resources (user cert...).
|
|
// One will have to pass VALID UserName, Domain and Password.
|
|
// Below all the three parameters are passed as NULL.
|
|
//
|
|
retCode = Impersonate(hTokenImpersonateUser,
|
|
NULL,
|
|
NULL,
|
|
NULL);
|
|
|
|
//
|
|
// Must call EapHostPeerInitialize() before calling any EapHostPeerXXX API.
|
|
//
|
|
retCode = funcEapHostPeerInitialize();
|
|
if(retCode != ERROR_SUCCESS)
|
|
{
|
|
//Error Reporting
|
|
goto Cleanup;
|
|
}
|
|
|
|
|
|
//
|
|
// Call BeginSession() in the EAP Host. This starts the authentication process.
|
|
//
|
|
retCode = funcBeginSession(
|
|
dwFlags, //Flags
|
|
eapType, //EAP_METHOD_TYPE
|
|
NULL, //EapAttributes
|
|
hTokenImpersonateUser, //HANDLE
|
|
sizeConnectionData, //Connection Data Size
|
|
connectionData, //Connection Data
|
|
sizeUserData, //User Data Size
|
|
userData, //User Data
|
|
MAX_PACKET_SIZE, //Max Packet
|
|
&connectionId, //ConnectionId
|
|
(NOTIFICATIONHANDLER)SdkEapNotificationHandler, //Notification Call Back Handler
|
|
(void*)threadId, //Context Data (Thread Identifier)
|
|
&eapSessionId, // Session Id
|
|
&pEapError
|
|
);
|
|
if(retCode != ERROR_SUCCESS)
|
|
{
|
|
//Error Reporting
|
|
funcFreeEapError(pEapError);
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Call EAPHost to process the received packet.
|
|
|
|
//
|
|
// First time this is called, the packet is received from NAS(Network Access Server)/Access Point.
|
|
// The packet is EAP-RequestIdentity.
|
|
// From then, the packet that is received is from the Authentication Server.
|
|
//
|
|
|
|
// Copy to the global variable.
|
|
retCode = AllocateMemory(g_dwIdentityRequestPacketLen, (PVOID *)&pReceivedPacket);
|
|
if(retCode != ERROR_SUCCESS)
|
|
{
|
|
//Report Error.
|
|
goto Cleanup;
|
|
}
|
|
|
|
CopyMemory(pReceivedPacket, g_pIdentityRequest, g_dwIdentityRequestPacketLen);
|
|
gdwReceivedPacketLen = g_dwIdentityRequestPacketLen;
|
|
|
|
//
|
|
// In a loop, take actions and and wait for next packet from authenticator until we receive
|
|
// the Result packet.
|
|
//
|
|
while(action != EapHostPeerResponseResult)
|
|
{
|
|
//
|
|
// Pass the received packet to EapMethod to process it.
|
|
//
|
|
retCode = funcProcessReceivedPacket(
|
|
eapSessionId, //Session Id
|
|
gdwReceivedPacketLen, //Length of the Packet
|
|
pReceivedPacket, //Packet
|
|
&action, //EapHostPeerResponseAction
|
|
&pEapError
|
|
);
|
|
if(retCode != ERROR_SUCCESS)
|
|
{
|
|
// Error Reporting
|
|
funcFreeEapError(pEapError);
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// EapMethod returns an action after processing the received packet.
|
|
// Supplicant should take appropriate action.
|
|
//
|
|
retCode = HandleEapHostPeerResponseAction(action);
|
|
if(retCode != ERROR_SUCCESS)
|
|
{
|
|
// Error Reporting
|
|
goto Cleanup;
|
|
}
|
|
|
|
retCode = FreeMemory((PVOID *)&pReceivedPacket);
|
|
if(retCode != ERROR_SUCCESS)
|
|
{
|
|
// Error Reporting
|
|
goto Cleanup;
|
|
}
|
|
gdwReceivedPacketLen = 0;
|
|
|
|
//
|
|
// Till we receive the Result packet, we should wait for an event which the listener thread
|
|
// will set when it receives a packet from authenticator.
|
|
//
|
|
|
|
}
|
|
|
|
Cleanup:
|
|
//
|
|
// userData, connectionData and pReceivedPacket all get freed in Cleanup().
|
|
//
|
|
return retCode;
|
|
}
|
|
|
|
//
|
|
// Demonstrate how UI calls need to be executed in a different thread.
|
|
// This function is the threadhandler that executes UI calls to EapHost.
|
|
//
|
|
DWORD HandleUICalls(LPVOID lpParameter)
|
|
{
|
|
DWORD retCode = ERROR_SUCCESS;
|
|
EAP_ERROR *pEapError = NULL;
|
|
HWND hwndParent = {0};
|
|
|
|
UNREFERENCED_PARAMETER(lpParameter);
|
|
|
|
retCode = GetEapMethodFromListOfInstalledEapMethods(&eapType);
|
|
if(retCode != ERROR_SUCCESS)
|
|
{
|
|
// Either EapHostPeerGetMethods reported an error or
|
|
// the required EapMethod is not installed.
|
|
goto Cleanup;
|
|
}
|
|
|
|
retCode = SetEvent(event_WaitforUIThread);
|
|
if(retCode == 0)
|
|
{
|
|
retCode = GetLastError();
|
|
goto Cleanup;
|
|
}
|
|
|
|
while(needToWaitForEvent == TRUE)
|
|
{
|
|
retCode = WaitForSingleObject(event_WaitforRuntimeThread, INFINITE);
|
|
if(retCode != WAIT_OBJECT_0) /// WAIT_FAILED, WAIT_ABANDONED, WAIT_TIMEOUT all error.
|
|
{
|
|
//Report Error.
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Always remember to free the memory that is allocated by EapHost CONFIG APIs.
|
|
// While those received from RUNTIME APIs will be freed automatically by EapHost
|
|
// when session ends.
|
|
//
|
|
dwSizeOfDataFromInteractiveUI = 0;
|
|
if(pDataFromInteractiveUI != NULL)
|
|
EapHostPeerFreeMemory(pDataFromInteractiveUI);
|
|
|
|
//
|
|
// Supplicant will always call InvokeInteractiveUI and EAPHost will take
|
|
// care of calling Identity and Interactive UI depending on the context.
|
|
//
|
|
retCode = EapHostPeerInvokeInteractiveUI(
|
|
hwndParent,
|
|
dwSizeofUIContextData,
|
|
pUIContextData,
|
|
&dwSizeOfDataFromInteractiveUI,
|
|
&pDataFromInteractiveUI,
|
|
&pEapError
|
|
);
|
|
if(retCode != ERROR_SUCCESS)
|
|
{
|
|
// Error Reporting
|
|
//Config APIs error should be freed using EapHostPeerFreeErrorMemory API.
|
|
EapHostPeerFreeErrorMemory(pEapError);
|
|
goto Cleanup;
|
|
}
|
|
|
|
retCode = SetEvent(event_WaitforUIThread);
|
|
if(retCode == 0)
|
|
{
|
|
retCode = GetLastError();
|
|
goto Cleanup;
|
|
}
|
|
|
|
}
|
|
|
|
Cleanup:
|
|
return retCode;
|
|
}
|
|
|
|
|
|
//
|
|
// Demonstrate retrieval and processing of EapAttributes.
|
|
//
|
|
DWORD HandleEapHostPeerResponseAction_Respond()
|
|
{
|
|
DWORD retCode = ERROR_SUCCESS;
|
|
EapAttributes in_attributes = {0};
|
|
EapAttributes *out_attributes = NULL;
|
|
EAP_ERROR *pEapError = NULL;
|
|
DWORD numInAttributes = 0;
|
|
DWORD numAttrCtr = 0;
|
|
DWORD isCorrectAttr = 0;
|
|
EapAttribute *pEapAttribute = NULL;
|
|
|
|
//
|
|
// Get the attributes from EapHost/EapMethod that needs to be processed by the supplicant.
|
|
//
|
|
retCode = funcGetRespAttributes(
|
|
eapSessionId,
|
|
&in_attributes,
|
|
&pEapError);
|
|
if(retCode != ERROR_SUCCESS)
|
|
{
|
|
// Error Reporting
|
|
funcFreeEapError(pEapError);
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
//Process the EapAtributes
|
|
//
|
|
numInAttributes = in_attributes.dwNumberOfAttributes;
|
|
for(numAttrCtr = 0; numAttrCtr < numInAttributes; numAttrCtr++)
|
|
{
|
|
pEapAttribute = (in_attributes.pAttribs) + numAttrCtr;
|
|
|
|
switch(pEapAttribute->eaType)
|
|
{
|
|
case 1:
|
|
|
|
isCorrectAttr = 1;
|
|
|
|
//
|
|
// Expected Attribute ---- eaType = 1; dwLength = 4; pValue = 40
|
|
//
|
|
{
|
|
DWORD attrLength = 0;
|
|
DWORD attrValue = 0;
|
|
attrLength = pEapAttribute->dwLength;
|
|
|
|
if(attrLength != sizeof(DWORD))
|
|
isCorrectAttr = 0;
|
|
|
|
attrValue = *((DWORD *)pEapAttribute->pValue);
|
|
|
|
if(attrValue != EAPTYPE)
|
|
isCorrectAttr = 0;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
EapTrace("Supplicant does not process attributes except EapAttrType = 1");
|
|
break;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Create the Response attribute. We need to call EapHostPeerSetResponseAttributes even if
|
|
// supplicant has no attributes to set. In that case call EapHostPeerSetResponseAttributes
|
|
// with EapAttributes having 0 eap attributes.
|
|
//
|
|
|
|
retCode = AllocateAttributes(1, &out_attributes);
|
|
if(retCode != ERROR_SUCCESS)
|
|
{
|
|
EapTrace("HandleEapHostPeerResponseAction_Respond : AllocateAttributes returned error = %d", retCode);
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Response Attribute --- eaType = 2, dwLength = 4, pValue = 1 (if isCorrectAttr == TRUE) else 0.
|
|
//
|
|
|
|
retCode = AddAttribute(out_attributes, (EapAttributeType)2, sizeof(DWORD), (PVOID)&isCorrectAttr);
|
|
if(retCode != ERROR_SUCCESS)
|
|
{
|
|
EapTrace("HandleEapHostPeerResponseAction_Respond : AddAttribute returned error = %d", retCode);
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Set the response attributes to EapMethod via EapHost.
|
|
//
|
|
retCode = funcSetRespAttributes(
|
|
eapSessionId,
|
|
out_attributes,
|
|
&action,
|
|
&pEapError
|
|
);
|
|
if(retCode != ERROR_SUCCESS)
|
|
{
|
|
// Error Reporting
|
|
funcFreeEapError(pEapError);
|
|
goto Cleanup;
|
|
}
|
|
|
|
retCode = HandleEapHostPeerResponseAction(action);
|
|
if(retCode != ERROR_SUCCESS)
|
|
{
|
|
// Error Reporting
|
|
goto Cleanup;
|
|
}
|
|
|
|
Cleanup:
|
|
//
|
|
// Free the out_attributes.
|
|
//
|
|
FreeAttributes(&out_attributes);
|
|
|
|
return retCode;
|
|
}
|
|
|
|
|
|
//
|
|
// Demonstrate what to do when EAP Host returns InvokeUI as the action to take.
|
|
//
|
|
DWORD HandleEapHostPeerResponseAction_InvokeUI()
|
|
{
|
|
DWORD retCode = ERROR_SUCCESS;
|
|
EAP_ERROR *pEapError = NULL;
|
|
|
|
//
|
|
// Get the current context of the EapMethod so that it can be passed to the
|
|
// EapHostPeerInvokeInteractiveUI API.
|
|
//
|
|
retCode = funcGetUIContext(
|
|
eapSessionId,
|
|
&dwSizeofUIContextData,
|
|
&pUIContextData,
|
|
&pEapError
|
|
);
|
|
if(retCode != ERROR_SUCCESS)
|
|
{
|
|
// Error Reporting
|
|
funcFreeEapError(pEapError);
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Invoke the UI thread as it needs to make a call to EapHostPeerInvokeInteractiveUI API.
|
|
//
|
|
retCode = SetEvent(event_WaitforRuntimeThread);
|
|
if(retCode == 0)
|
|
{
|
|
retCode = GetLastError();
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Wait till the UI thread completes.
|
|
//
|
|
retCode = WaitForSingleObject(event_WaitforUIThread, INFINITE);
|
|
if(retCode != WAIT_OBJECT_0) /// WAIT_FAILED, WAIT_ABANDONED, WAIT_TIMEOUT all error.
|
|
{
|
|
//Report Error.
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Set the context data received from the above call of EapHostPeerInvokeInteractiveUI.
|
|
//
|
|
retCode = funcSetUIContext(
|
|
eapSessionId,
|
|
dwSizeOfDataFromInteractiveUI,
|
|
pDataFromInteractiveUI,
|
|
&action,
|
|
&pEapError
|
|
);
|
|
if(retCode != ERROR_SUCCESS)
|
|
{
|
|
// Error Reporting
|
|
funcFreeEapError(pEapError);
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// EapHost returns an action to take depending on the context data passed to it.
|
|
//
|
|
retCode = HandleEapHostPeerResponseAction(action);
|
|
if(retCode != ERROR_SUCCESS)
|
|
{
|
|
// Error Reporting
|
|
goto Cleanup;
|
|
}
|
|
|
|
Cleanup:
|
|
return retCode;
|
|
}
|
|
|
|
|
|
//
|
|
// Demonstrate what to do when EAP Host returns GetResult as the action to take.
|
|
//
|
|
DWORD HandleEapHostPeerResponseAction_GetResult(EapHostPeerMethodResultReason reason)
|
|
{
|
|
DWORD retCode = ERROR_SUCCESS;
|
|
EAP_ERROR *pEapError = NULL;
|
|
EapHostPeerMethodResult result = {0};
|
|
|
|
//
|
|
// Get the final result of authentication from EapHost.
|
|
//
|
|
retCode = funcGetResult(
|
|
eapSessionId,
|
|
reason,
|
|
&result,
|
|
&pEapError
|
|
);
|
|
if(retCode != ERROR_SUCCESS)
|
|
{
|
|
// Error Reporting
|
|
funcFreeEapError(pEapError);
|
|
goto Cleanup;
|
|
}
|
|
|
|
if(result.fIsSuccess == TRUE)
|
|
{
|
|
//
|
|
// Save User and Connection Blob if indicated in the EapHostPeerMethodResult structure.
|
|
//
|
|
if(result.fSaveConnectionData == TRUE)
|
|
{
|
|
//
|
|
// Store the Connection Data into Registry. This BLOB will be used next time when
|
|
// authentication is done.
|
|
// Registry is just an example of a place to store it.
|
|
// In this example we store raw data in regsitry. One may wish to encrypt it.
|
|
//
|
|
retCode = StoreBLOBInRegistry(CONNECTION_PROPERTIES,
|
|
result.dwSizeofConnectionData,
|
|
result.pConnectionData);
|
|
if(retCode != ERROR_SUCCESS)
|
|
{
|
|
EapTrace("StoreBLOBInRegistry returned error = %d",retCode);
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
if(result.fSaveUserData == TRUE)
|
|
{
|
|
//
|
|
// Store the User Data into Registry. This BLOB will be used next time when
|
|
// authentication is done.
|
|
// Registry is just an example of a place to store it.
|
|
// In this example we store raw data in regsitry. One may wish to encrypt it.
|
|
//
|
|
retCode = StoreBLOBInRegistry(USER_PROPERTIES,
|
|
result.dwSizeofUserData,
|
|
result.pUserData);
|
|
if(retCode != ERROR_SUCCESS)
|
|
{
|
|
EapTrace("StoreBLOBInRegistry returned error = %d",retCode);
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Delete the registry entries for Config and User Blob if present when authentication fails
|
|
// using those values.
|
|
//
|
|
retCode = DeleteBLOBFromRegistry();
|
|
if(retCode != ERROR_SUCCESS)
|
|
{
|
|
EapTrace("DeleteBLOBFromRegistry returned error = %d",retCode);
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Process the result and accordingly take further steps.
|
|
//
|
|
|
|
|
|
Cleanup:
|
|
return retCode;
|
|
}
|
|
|
|
|
|
//
|
|
// Demonstrate how the supplicant gets a response from EAP Host and sends to the authenticator.
|
|
//
|
|
DWORD HandleEapHostPeerResponseAction_Send()
|
|
{
|
|
DWORD retCode = ERROR_SUCCESS;
|
|
DWORD size = 0;
|
|
BYTE* pSendPacket = NULL;
|
|
EAP_ERROR *pEapError = NULL;
|
|
|
|
//
|
|
// Get the response packet from EapHost.
|
|
//
|
|
retCode = funcGetSendPacket(eapSessionId, &size, &pSendPacket, &pEapError);
|
|
if(retCode != ERROR_SUCCESS)
|
|
{
|
|
//Report Error
|
|
funcFreeEapError(pEapError);
|
|
goto Cleanup;
|
|
}
|
|
|
|
retCode = SendEapPackets(pSendPacket, size);
|
|
if(retCode != ERROR_SUCCESS)
|
|
{
|
|
//Report Error
|
|
goto Cleanup;
|
|
}
|
|
|
|
Cleanup:
|
|
return retCode;
|
|
}
|
|
|
|
|
|
//
|
|
//Clean up the resources used for authentication.
|
|
//
|
|
void CleanUp()
|
|
{
|
|
DWORD retCode = ERROR_SUCCESS;
|
|
EAP_ERROR *pEapError = NULL;
|
|
|
|
//
|
|
// This function needs to be called only if BeginSession succeeds.
|
|
// Call Clear Connection. Same connection can be used across multiple connections.
|
|
//
|
|
if(eapSessionId)
|
|
{
|
|
retCode = funcClearConnection((GUID *)&connectionId, &pEapError);
|
|
if(retCode != ERROR_SUCCESS)
|
|
{
|
|
// Report Error
|
|
funcFreeEapError(pEapError);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Last thing to do with the EapHost is to call PeerUninitialize.
|
|
//
|
|
funcEapHostPeerUninitialize();
|
|
|
|
//
|
|
// Finally, Unload the eapprxy.dll
|
|
//
|
|
FreeLibrary(handleLibrary);
|
|
|
|
//
|
|
// Free the resources.
|
|
//
|
|
if(event_ReceivePacket)
|
|
CloseHandle(event_ReceivePacket);
|
|
if(event_WaitforRuntimeThread)
|
|
CloseHandle(event_WaitforRuntimeThread);
|
|
if(event_WaitforUIThread)
|
|
CloseHandle(event_WaitforUIThread);
|
|
if(UIThreadHandle)
|
|
CloseHandle(UIThreadHandle);
|
|
|
|
if(userData)
|
|
FreeMemory((PVOID *)&userData);
|
|
if(connectionData)
|
|
FreeMemory((PVOID *)&connectionData);
|
|
if(pReceivedPacket)
|
|
FreeMemory((PVOID *)&pReceivedPacket);
|
|
|
|
eapSessionId = 0;
|
|
}
|
|
|
|
//
|
|
// Demonstrate how to send and receive packet to/from supplicant to the authenticator.
|
|
//
|
|
|
|
DWORD SendEapPackets(BYTE *pEapPacketToSend, DWORD dwPacketSize)
|
|
{
|
|
DWORD retCode = ERROR_SUCCESS;
|
|
|
|
UNREFERENCED_PARAMETER(pEapPacketToSend);
|
|
UNREFERENCED_PARAMETER(dwPacketSize);
|
|
|
|
//
|
|
// Code that wrap the EapPacket within 802.1x (in case of Access Point) or PPP (in case of Remote Access Server).
|
|
// and sends it to the respective NAS.
|
|
//
|
|
|
|
return retCode;
|
|
}
|
|
|
|
DWORD ReceiveEapPackets(LPVOID lpParameter)
|
|
{
|
|
DWORD retCode = ERROR_SUCCESS;
|
|
|
|
UNREFERENCED_PARAMETER(lpParameter);
|
|
|
|
while(needToWaitForEvent == TRUE)
|
|
{
|
|
//
|
|
// Code that will listen for packets from the NAS (Access Point or Remote Access Server).
|
|
//
|
|
|
|
//
|
|
// Once a packet is received, copy the received packet to variable pReceivedPacket and
|
|
// update gdwReceivedPacketLen.
|
|
//
|
|
|
|
//
|
|
// Set the event "event_ReceivePacket" so that authenticator thread wakes up and process
|
|
// the recived packet by passing it to EapHost.
|
|
//
|
|
|
|
}
|
|
|
|
return retCode;
|
|
}
|
|
|
|
|
|
//
|
|
// Demonstrate how to create IXMLDOMDocument2 interface pointer and load an xml file using it.
|
|
//
|
|
DWORD CreateDOMDocumentFromXML(DWORD type, IXMLDOMDocument2** pXMLDOMDocument2)
|
|
{
|
|
DWORD retCode = ERROR_SUCCESS;
|
|
HRESULT hr = S_OK;
|
|
VARIANT xmlFileNameVariant = {0};
|
|
VARIANT_BOOL isSuccessful = {0};
|
|
|
|
hr = CoInitialize(NULL);
|
|
if(FAILED(hr))
|
|
{
|
|
//Report Error
|
|
retCode = HRESULT_CODE(hr);
|
|
goto Cleanup;
|
|
}
|
|
|
|
hr = CoCreateInstance(CLSID_DOMDocument60, NULL, CLSCTX_INPROC_SERVER,
|
|
IID_IXMLDOMDocument2, (void**)&(*pXMLDOMDocument2));
|
|
if(FAILED(hr))
|
|
{
|
|
//Report Error
|
|
retCode = HRESULT_CODE(hr);
|
|
goto Cleanup;
|
|
}
|
|
|
|
xmlFileNameVariant.vt = VT_BSTR;
|
|
|
|
switch(type)
|
|
{
|
|
case CONNECTION_PROPERTIES:
|
|
xmlFileNameVariant.bstrVal = SysAllocString(L"SdkEapConn.xml");
|
|
if(xmlFileNameVariant.bstrVal == NULL)
|
|
{
|
|
retCode = ERROR_OUTOFMEMORY;
|
|
goto Cleanup;
|
|
}
|
|
break;
|
|
|
|
case USER_PROPERTIES:
|
|
xmlFileNameVariant.bstrVal = SysAllocString(L"SdkEapUser.xml");
|
|
if(xmlFileNameVariant.bstrVal == NULL)
|
|
{
|
|
retCode = ERROR_OUTOFMEMORY;
|
|
goto Cleanup;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
//Report Error
|
|
break;
|
|
}
|
|
|
|
hr = (*pXMLDOMDocument2)->load(xmlFileNameVariant, &isSuccessful);
|
|
if(FAILED(hr)) //Fails if isSuccessful is VARIANT_FALSE, no need to especially check isSuccessful
|
|
{
|
|
//Report Error
|
|
retCode = HRESULT_CODE(hr);
|
|
goto Cleanup;
|
|
}
|
|
|
|
Cleanup:
|
|
SysFreeString(xmlFileNameVariant.bstrVal);
|
|
|
|
return retCode;
|
|
}
|
|
|
|
//
|
|
// Generates the Identity Request EAP Packet.
|
|
//
|
|
DWORD MakeIdentityRequestMessage(BYTE **pIdentityReq, DWORD *pIdentityReqLen)
|
|
{
|
|
DWORD retCode = ERROR_SUCCESS;
|
|
EapPacket *pIdentityReqEapPacket = NULL;
|
|
WORD tempPacketLen = 0;
|
|
|
|
*pIdentityReqLen = 5; //Code = 1, Id = 1, Length = 2, Type = 1
|
|
|
|
//
|
|
//Allocate the buffer to store Identity Request packet.
|
|
//
|
|
retCode = AllocateMemory(*pIdentityReqLen, (PVOID *)&pIdentityReqEapPacket);
|
|
if(retCode != ERROR_SUCCESS)
|
|
{
|
|
//Report Error
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Fill the elments of the Identity Request Packet.
|
|
//
|
|
pIdentityReqEapPacket->Code = EapCodeRequest;
|
|
pIdentityReqEapPacket->Id = 0;
|
|
HostToWireFormat16((WORD)*pIdentityReqLen, (PBYTE)&tempPacketLen);
|
|
CopyMemory(&(pIdentityReqEapPacket->Length), &tempPacketLen, sizeof(WORD));
|
|
pIdentityReqEapPacket->Data[0] = 0x01; //Identity Request Type
|
|
|
|
*pIdentityReq = (BYTE *)pIdentityReqEapPacket;
|
|
|
|
Cleanup:
|
|
return retCode;
|
|
}
|
|
|
|
|
|
//
|
|
// Do logon user and then impersonate the user.
|
|
// pToken is the handle returned after impersonation.
|
|
// UserName, Password and Domain are required for Logon.
|
|
//
|
|
DWORD Impersonate(HANDLE &pToken,
|
|
LPCWSTR user,
|
|
LPCWSTR domain,
|
|
LPCWSTR pswd)
|
|
{
|
|
DWORD retCode = ERROR_SUCCESS;
|
|
BOOL fOk = FALSE;
|
|
|
|
fOk = LogonUserW( user,
|
|
domain,
|
|
pswd,
|
|
LOGON32_LOGON_INTERACTIVE,
|
|
LOGON32_PROVIDER_DEFAULT,
|
|
&pToken );
|
|
|
|
if ( fOk == FALSE )
|
|
{
|
|
retCode = GetLastError( );
|
|
//Report Error
|
|
goto Cleanup;
|
|
}
|
|
|
|
fOk = ImpersonateLoggedOnUser(pToken);
|
|
if ( fOk == FALSE )
|
|
{
|
|
retCode = GetLastError( );
|
|
//Report Error
|
|
goto Cleanup;
|
|
}
|
|
|
|
Cleanup:
|
|
return retCode;
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
// Stores the BLOB (either UserData or ConnectionData) into registry. The supplicant does not care about the
|
|
// content of the BLOB.
|
|
//
|
|
DWORD StoreBLOBInRegistry(DWORD type, DWORD sizeOfBLOB, BYTE *pBLOB)
|
|
{
|
|
DWORD retCode = ERROR_SUCCESS;
|
|
HKEY hKeyEap = NULL;
|
|
bool regkeyopen = false;
|
|
LPDWORD lpdwDisposition = NULL;
|
|
|
|
//
|
|
// Create the Registry Key --- HKLM\Software\SdkEap if not created before.
|
|
// Location is picked randomly.
|
|
//
|
|
retCode = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
|
|
L"SOFTWARE\\SdkEap",
|
|
0,
|
|
KEY_ALL_ACCESS,
|
|
&hKeyEap);
|
|
if (retCode != ERROR_SUCCESS)
|
|
{
|
|
if (retCode == ERROR_FILE_NOT_FOUND)
|
|
{
|
|
// Create a new key.
|
|
retCode = RegCreateKeyEx(HKEY_LOCAL_MACHINE,
|
|
L"SOFTWARE\\SdkEap",
|
|
0,
|
|
NULL,
|
|
REG_OPTION_VOLATILE,
|
|
KEY_ALL_ACCESS,
|
|
NULL,
|
|
&hKeyEap,
|
|
lpdwDisposition);
|
|
if (retCode != ERROR_SUCCESS)
|
|
{
|
|
EapTrace("StoreBLOBInRegistry: Could not open reg key");
|
|
goto Cleanup;
|
|
}
|
|
regkeyopen = true;
|
|
}
|
|
else
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Set Values in to the above Registry Key.
|
|
//
|
|
wchar_t wszKey[128] = { L'\0' };
|
|
if(type == CONNECTION_PROPERTIES)
|
|
swprintf_s( wszKey, 128, L"ConnectionBlob_%u", EAPTYPE );
|
|
else
|
|
swprintf_s( wszKey, 128, L"UserBlob_%u", EAPTYPE );
|
|
|
|
retCode = RegSetValueEx(
|
|
hKeyEap,
|
|
wszKey,
|
|
0,
|
|
REG_BINARY,
|
|
pBLOB,
|
|
sizeOfBLOB
|
|
);
|
|
if ( retCode != ERROR_SUCCESS )
|
|
{
|
|
EapTrace("StoreBLOBInRegistry: Cannot set regkey %d", retCode);
|
|
goto Cleanup;
|
|
}
|
|
|
|
RegCloseKey(hKeyEap);
|
|
|
|
Cleanup:
|
|
return retCode;
|
|
}
|
|
|
|
|
|
//
|
|
// Retrieves the BLOB (either UserData or ConnectionData) from registry. The supplicant does not care about the
|
|
// content of the BLOB.
|
|
//
|
|
DWORD ReadBLOBFromRegistry(DWORD type, DWORD &sizeOfBLOB, BYTE *&pBLOB)
|
|
{
|
|
DWORD retCode = ERROR_SUCCESS;
|
|
HKEY hKeyEap = NULL;
|
|
DWORD dwRegValueType = REG_BINARY;
|
|
DWORD cbRegValueData = 0;
|
|
|
|
//
|
|
// Open the registry value.
|
|
//
|
|
retCode = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
|
|
L"SOFTWARE\\SdkEap",
|
|
0,
|
|
KEY_READ,
|
|
&hKeyEap);
|
|
if (retCode != ERROR_SUCCESS)
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
else
|
|
{
|
|
wchar_t wszKey[128] = { L'\0' };
|
|
if(type == CONNECTION_PROPERTIES)
|
|
swprintf_s( wszKey, 128, L"ConnectionBlob_%u", EAPTYPE );
|
|
else
|
|
swprintf_s( wszKey, 128, L"UserBlob_%u", EAPTYPE );
|
|
|
|
//
|
|
// Read the specified regvalue.
|
|
//
|
|
retCode = RegQueryValueExW(hKeyEap, wszKey, NULL, &dwRegValueType,
|
|
NULL, &cbRegValueData);
|
|
if (retCode != ERROR_SUCCESS)
|
|
{
|
|
goto Cleanup;
|
|
}
|
|
else if (retCode == ERROR_SUCCESS && cbRegValueData > 0)
|
|
{
|
|
//
|
|
// Make sure it's the right type.
|
|
//
|
|
if (dwRegValueType != REG_BINARY)
|
|
{
|
|
EapTrace("ReadBLOBFromRegistry(): Error -- registry value was not a string! Ignoring it.");
|
|
retCode = NO_ERROR;
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Allocate a buffer to hold the value.
|
|
retCode = AllocateMemory(cbRegValueData, (PVOID*)&pBLOB);
|
|
if (retCode != NO_ERROR)
|
|
goto Cleanup;
|
|
|
|
// Get the real value. (Errors handled by code outside if() clause.)
|
|
retCode = RegQueryValueExW(hKeyEap, wszKey, NULL, &dwRegValueType,
|
|
(PBYTE)pBLOB, &cbRegValueData);
|
|
if (retCode != ERROR_SUCCESS)
|
|
{
|
|
EapTrace("ReadBLOBFromRegistry(): Error -- couldn't get registry value! (error %d)", retCode);
|
|
goto Cleanup;
|
|
}
|
|
|
|
sizeOfBLOB = cbRegValueData;
|
|
}
|
|
}
|
|
|
|
Cleanup:
|
|
if (hKeyEap)
|
|
RegCloseKey(hKeyEap);
|
|
|
|
// If returning error, free any memory we allocated.
|
|
if (retCode != NO_ERROR)
|
|
FreeMemory((PVOID*)&pBLOB);
|
|
|
|
return retCode;
|
|
}
|
|
|
|
|
|
//
|
|
// Delete the registry entries for Config and User Blob if present when authentication fails
|
|
// using those values.
|
|
//
|
|
DWORD DeleteBLOBFromRegistry()
|
|
{
|
|
DWORD retCode = ERROR_SUCCESS;
|
|
HKEY hkEapBlob = 0;
|
|
const wchar_t eapRootKeyName[] = L"Software";
|
|
const wchar_t eapKeyName[] = L"SdkEap";
|
|
|
|
// Check if the key -- "HKLM\Software" exist.
|
|
retCode = RegOpenKeyEx(HKEY_LOCAL_MACHINE, eapRootKeyName,
|
|
0, KEY_ALL_ACCESS, &hkEapBlob);
|
|
if(retCode != ERROR_SUCCESS)
|
|
{
|
|
// Trace Error
|
|
goto Cleanup;
|
|
}
|
|
|
|
// Delete the subkey - "SdkEap" which
|
|
retCode = RegDeleteKeyW(hkEapBlob, eapKeyName);
|
|
if(retCode != ERROR_SUCCESS)
|
|
{
|
|
// Trace Error
|
|
goto Cleanup;
|
|
}
|
|
|
|
Cleanup:
|
|
if(hkEapBlob)
|
|
RegCloseKey(hkEapBlob);
|
|
|
|
return retCode;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
// Dialog routines.
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Display and Configure Eap Method() helper function
|
|
*
|
|
* This function displays an UI (the IDD_DIALOG dialog box) that displays the list of
|
|
* installed EapMethods and Eap Properties supported by each of them.
|
|
*
|
|
* @param hwndParent [in] Handle to the parent window for the
|
|
* user interface dialog.
|
|
*
|
|
*
|
|
*
|
|
* @return If the function succeeds, the return value is NO_ERROR.
|
|
*/
|
|
DWORD
|
|
DisplayInstalledEapMethods(
|
|
IN HWND hwndParent,
|
|
IN EAP_METHOD_INFO_ARRAY* pEapMethodsData
|
|
)
|
|
{
|
|
DWORD dwErr = NO_ERROR;
|
|
int result = 0;
|
|
|
|
result = (int)DialogBoxParam(
|
|
GetModuleHandle(NULL),
|
|
MAKEINTRESOURCE(IDD_DIALOG),
|
|
hwndParent,
|
|
DialogProc,
|
|
(LPARAM)pEapMethodsData);
|
|
|
|
if (result < 0)
|
|
{
|
|
dwErr = GetLastError();
|
|
EapTrace("Hit error %d while displaying DisplayInstalledEapMethods dialog!",
|
|
dwErr);
|
|
}
|
|
|
|
return(dwErr);
|
|
}
|
|
|
|
|
|
INT_PTR CALLBACK DialogProc(
|
|
IN HWND hWnd,
|
|
IN UINT unMsg,
|
|
IN WPARAM wParam,
|
|
IN LPARAM lParam
|
|
)
|
|
{
|
|
DWORD dwErr = FALSE; // By default, return FALSE.
|
|
EAP_METHOD_INFO_ARRAY* pEapMethodsData = NULL;
|
|
|
|
switch (unMsg)
|
|
{
|
|
case WM_INITDIALOG:
|
|
dwErr = InitDialog(hWnd, lParam);
|
|
break;
|
|
|
|
case WM_COMMAND:
|
|
pEapMethodsData = (EAP_METHOD_INFO_ARRAY *)((LONG_PTR)GetWindowLongPtr(hWnd, DWLP_USER));
|
|
dwErr = CommandProc(pEapMethodsData, LOWORD(wParam), hWnd);
|
|
break;
|
|
}
|
|
|
|
return(dwErr);
|
|
}
|
|
|
|
|
|
BOOL InitDialog(
|
|
IN HWND hWndDlg,
|
|
IN LPARAM lParam
|
|
)
|
|
{
|
|
DWORD dwNumberOfEapMethods = 0;
|
|
DWORD dwMethodCount = 0;
|
|
EAP_METHOD_INFO *pLocalEapMethodInfo = NULL;
|
|
HWND hWnd = NULL;
|
|
EAP_METHOD_INFO_ARRAY *eapMethodsInfo = NULL;
|
|
LRESULT lResult = 0;
|
|
|
|
SetWindowLongPtr(hWndDlg, DWLP_USER, (LONG)lParam);
|
|
|
|
eapMethodsInfo = (EAP_METHOD_INFO_ARRAY *)lParam;
|
|
|
|
hWnd = GetDlgItem(hWndDlg, IDC_METHODLIST);
|
|
if (hWnd == NULL) // GetDlgItem() returns NULL for errors.
|
|
{
|
|
EapTrace("Error -- InitDialog (error %d)",GetLastError());
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Display the list of Eap Methods Installed with EapHost.
|
|
//
|
|
dwNumberOfEapMethods = eapMethodsInfo->dwNumberOfMethods;
|
|
|
|
for(dwMethodCount = 0; dwMethodCount < dwNumberOfEapMethods; dwMethodCount++)
|
|
{
|
|
pLocalEapMethodInfo = &((eapMethodsInfo->pEapMethods)[dwMethodCount]);
|
|
|
|
lResult = SendMessage(hWnd,
|
|
(UINT) LB_ADDSTRING,
|
|
(WPARAM) 0,
|
|
(LPARAM)pLocalEapMethodInfo->pwszFriendlyName);
|
|
if(lResult == LB_ERR)
|
|
goto Cleanup;
|
|
|
|
}
|
|
|
|
Cleanup:
|
|
return(FALSE);
|
|
}
|
|
|
|
BOOL
|
|
CommandProc(
|
|
IN EAP_METHOD_INFO_ARRAY* pEapMethodsData,
|
|
IN WORD wId,
|
|
IN HWND hWndDlg
|
|
)
|
|
{
|
|
BOOL fOk = FALSE;
|
|
HWND hWnd = NULL;
|
|
DWORD retCode = ERROR_SUCCESS;
|
|
DWORD selIndex = 0;
|
|
EAP_ERROR *pEapError = NULL;
|
|
EAP_METHOD_INFO *pLocalEapMethodInfo = NULL;
|
|
|
|
WCHAR eapPropText[1000] = {L'\0'};
|
|
DWORD eapPropTextCtr = 0;
|
|
DWORD eapPropLen = 0;
|
|
|
|
// Sanity checks.
|
|
if (! pEapMethodsData)
|
|
{
|
|
EapTrace("CommandProc: input dialog pointer is NULL!");
|
|
goto Cleanup;
|
|
}
|
|
|
|
//
|
|
// Get the index of the selected EapMethod. Done above the switch block as
|
|
// this step is required for both IDC_PROPERTIES and IDC_CONFIGURE.
|
|
//
|
|
hWnd = GetDlgItem(hWndDlg, IDC_METHODLIST);
|
|
if (hWnd == NULL) // GetDlgItem() returns NULL for errors.
|
|
{
|
|
EapTrace("Error -- CommandProc -- IDC_METHODLIST -- (error %d)",GetLastError());
|
|
goto Cleanup;
|
|
}
|
|
|
|
selIndex = (DWORD)SendMessage(hWnd,
|
|
(UINT) LB_GETCURSEL,
|
|
(WPARAM) 0,
|
|
(LPARAM) 0);
|
|
if(selIndex == LB_ERR)
|
|
goto Cleanup;
|
|
|
|
pLocalEapMethodInfo = &((pEapMethodsData->pEapMethods)[selIndex]);
|
|
|
|
switch(wId)
|
|
{
|
|
case IDC_METHODLIST:
|
|
//
|
|
// We already know about the index that is selected.
|
|
// 1. Remove the Eap Properties corresponding to the earlier selected text.
|
|
// 2. Check if the new selected Eap suppports configuration. Accordingly, enable/disable the
|
|
// the Configure button.
|
|
|
|
// 1.
|
|
hWnd = GetDlgItem(hWndDlg, IDC_PROPERTYTEXT);
|
|
if (hWnd == NULL) // GetDlgItem() returns NULL for errors.
|
|
{
|
|
EapTrace("Error -- CommandProc -- IDC_PROPERTYTEXT -- (error %d)",GetLastError());
|
|
goto Cleanup;
|
|
}
|
|
// eapPropText = NULL
|
|
fOk = SetWindowText(hWnd, eapPropText);
|
|
if(fOk == 0)
|
|
goto Cleanup;
|
|
|
|
// 2.
|
|
hWnd = GetDlgItem(hWndDlg, IDC_CONFIGURE);
|
|
if (hWnd == NULL) // GetDlgItem() returns NULL for errors.
|
|
{
|
|
EapTrace("Error -- CommandProc -- IDC_CONFIGURE -- (error %d)",GetLastError());
|
|
goto Cleanup;
|
|
}
|
|
{
|
|
BOOL isHighlighted = FALSE;
|
|
if(!(pLocalEapMethodInfo->eapProperties & eapPropSupportsConfig))
|
|
{
|
|
isHighlighted = TRUE;
|
|
}
|
|
SendMessage(hWnd,
|
|
(UINT) BM_SETSTATE,
|
|
(WPARAM) isHighlighted,
|
|
(LPARAM) 0);
|
|
}
|
|
|
|
break;
|
|
|
|
case IDC_PROPERTIES:
|
|
//
|
|
// Display the Properties that the selected Eap Method supports
|
|
//
|
|
|
|
hWnd = GetDlgItem(hWndDlg, IDC_PROPERTYTEXT);
|
|
if (hWnd == NULL) // GetDlgItem() returns NULL for errors.
|
|
{
|
|
EapTrace("Error -- CommandProc -- IDC_PROPERTYTEXT -- (error %d)",GetLastError());
|
|
goto Cleanup;
|
|
}
|
|
|
|
eapPropLen = sizeof(eapPropText)/sizeof(wchar_t);
|
|
|
|
if(pLocalEapMethodInfo->eapProperties & eapPropCipherSuiteNegotiation)
|
|
eapPropTextCtr = swprintf_s(eapPropText, eapPropLen, L"CipherSuiteNegotiation \n");
|
|
if(pLocalEapMethodInfo->eapProperties & eapPropMutualAuth)
|
|
eapPropTextCtr += swprintf_s(eapPropText + eapPropTextCtr, eapPropLen - eapPropTextCtr, L"Mutual Authentication \n");
|
|
if(pLocalEapMethodInfo->eapProperties & eapPropIntegrity)
|
|
eapPropTextCtr += swprintf_s(eapPropText + eapPropTextCtr, eapPropLen - eapPropTextCtr, L"Integrity \n");
|
|
if(pLocalEapMethodInfo->eapProperties & eapPropReplayProtection)
|
|
eapPropTextCtr += swprintf_s(eapPropText + eapPropTextCtr, eapPropLen - eapPropTextCtr, L"Replay Protection \n");
|
|
if(pLocalEapMethodInfo->eapProperties & eapPropConfidentiality)
|
|
eapPropTextCtr += swprintf_s(eapPropText + eapPropTextCtr, eapPropLen - eapPropTextCtr, L"Confidentiality \n");
|
|
if(pLocalEapMethodInfo->eapProperties & eapPropKeyDerivation)
|
|
eapPropTextCtr += swprintf_s(eapPropText + eapPropTextCtr, eapPropLen - eapPropTextCtr, L"Key Derivation \n");
|
|
if(pLocalEapMethodInfo->eapProperties & eapPropKeyStrength64)
|
|
eapPropTextCtr += swprintf_s(eapPropText + eapPropTextCtr, eapPropLen - eapPropTextCtr, L"KeyStrength64 \n");
|
|
if(pLocalEapMethodInfo->eapProperties & eapPropKeyStrength128)
|
|
eapPropTextCtr += swprintf_s(eapPropText + eapPropTextCtr, eapPropLen - eapPropTextCtr, L"KeyStrength128 \n");
|
|
if(pLocalEapMethodInfo->eapProperties & eapPropKeyStrength256)
|
|
eapPropTextCtr += swprintf_s(eapPropText + eapPropTextCtr, eapPropLen - eapPropTextCtr, L"KeyStrength256 \n");
|
|
if(pLocalEapMethodInfo->eapProperties & eapPropKeyStrength512)
|
|
eapPropTextCtr += swprintf_s(eapPropText + eapPropTextCtr, eapPropLen - eapPropTextCtr, L"KeyStrength512 \n");
|
|
if(pLocalEapMethodInfo->eapProperties & eapPropKeyStrength1024)
|
|
eapPropTextCtr += swprintf_s(eapPropText + eapPropTextCtr, eapPropLen - eapPropTextCtr, L"KeyStrength1024 \n");
|
|
if(pLocalEapMethodInfo->eapProperties & eapPropDictionaryAttackResistance)
|
|
eapPropTextCtr += swprintf_s(eapPropText + eapPropTextCtr, eapPropLen - eapPropTextCtr, L"DictionaryAttackResistance \n");
|
|
if(pLocalEapMethodInfo->eapProperties & eapPropFastReconnect)
|
|
eapPropTextCtr += swprintf_s(eapPropText + eapPropTextCtr, eapPropLen - eapPropTextCtr, L"FastReconnect \n");
|
|
if(pLocalEapMethodInfo->eapProperties & eapPropCryptoBinding)
|
|
eapPropTextCtr += swprintf_s(eapPropText + eapPropTextCtr, eapPropLen - eapPropTextCtr, L"CryptoBinding \n");
|
|
if(pLocalEapMethodInfo->eapProperties & eapPropSessionIndependence)
|
|
eapPropTextCtr += swprintf_s(eapPropText + eapPropTextCtr, eapPropLen - eapPropTextCtr, L"SessionIndependence \n");
|
|
if(pLocalEapMethodInfo->eapProperties & eapPropFragmentation)
|
|
eapPropTextCtr += swprintf_s(eapPropText + eapPropTextCtr, eapPropLen - eapPropTextCtr, L"Fragmentation \n");
|
|
if(pLocalEapMethodInfo->eapProperties & eapPropChannelBinding)
|
|
eapPropTextCtr += swprintf_s(eapPropText + eapPropTextCtr, eapPropLen - eapPropTextCtr, L"ChannelBinding \n");
|
|
if(pLocalEapMethodInfo->eapProperties & eapPropNap)
|
|
eapPropTextCtr += swprintf_s(eapPropText + eapPropTextCtr, eapPropLen - eapPropTextCtr, L"Supports Nap \n");
|
|
if(pLocalEapMethodInfo->eapProperties & eapPropStandalone)
|
|
eapPropTextCtr += swprintf_s(eapPropText + eapPropTextCtr, eapPropLen - eapPropTextCtr, L"Standalone \n");
|
|
if(pLocalEapMethodInfo->eapProperties & eapPropMppeEncryption)
|
|
eapPropTextCtr += swprintf_s(eapPropText + eapPropTextCtr, eapPropLen - eapPropTextCtr, L"MppeEncryption \n");
|
|
if(pLocalEapMethodInfo->eapProperties & eapPropTunnelMethod)
|
|
eapPropTextCtr += swprintf_s(eapPropText + eapPropTextCtr, eapPropLen - eapPropTextCtr, L"TunnelMethod \n");
|
|
if(pLocalEapMethodInfo->eapProperties & eapPropSupportsConfig)
|
|
eapPropTextCtr += swprintf_s(eapPropText + eapPropTextCtr, eapPropLen - eapPropTextCtr, L"SupportsConfig \n");
|
|
|
|
fOk = SetWindowText(hWnd, eapPropText);
|
|
if(fOk == 0)
|
|
goto Cleanup;
|
|
|
|
break;
|
|
|
|
case IDC_CONFIGURE:
|
|
//
|
|
// Invoke the ConfigUI for the selected Eap Method.
|
|
//
|
|
retCode = EapHostPeerInvokeConfigUI(
|
|
hWndDlg,
|
|
0,
|
|
pLocalEapMethodInfo->eaptype,
|
|
0,
|
|
NULL,
|
|
&sizeConnectionData,
|
|
&connectionData,
|
|
&pEapError);
|
|
if(retCode != ERROR_SUCCESS)
|
|
{
|
|
//Report Error
|
|
EapHostPeerFreeErrorMemory(pEapError);
|
|
goto Cleanup;
|
|
}
|
|
break;
|
|
|
|
case IDC_EXITDIALOG:
|
|
EndDialog(hWndDlg, wId);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
}
|
|
|
|
fOk = TRUE;
|
|
|
|
Cleanup:
|
|
return fOk;
|
|
}
|
|
|
|
|