//+--------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 2001 // // File: U T I L. C // // Contents: Utility functions // // //---------------------------------------------------------------------------- #include "util.h" //+--------------------------------------------------------------------------- // // EAPOL related util functions // //+--------------------------------------------------------------------------- // EAP configuration registry definitions. static WCHAR REGKEY_Eap[] = L"System\\CurrentControlSet\\Services\\Rasman\\PPP\\EAP"; static WCHAR REGVAL_szFriendlyName[] = L"FriendlyName"; static WCHAR REGVAL_szConfigDll[] = L"ConfigUIPath"; static WCHAR REGVAL_szIdentityDll[] = L"IdentityPath"; static WCHAR REGVAL_fRequirePwd[] = L"InvokePasswordDialog"; static WCHAR REGVAL_fRequireUser[] = L"InvokeUsernameDialog"; static WCHAR REGVAL_pData[] = L"ConfigData"; static WCHAR REGVAL_fForceConfig[] = L"RequireConfigUI"; static WCHAR REGVAL_fMppeSupported[] = L"MPPEEncryptionSupported"; // Location of User blob #define cwszEapKeyEapolUser L"Software\\Microsoft\\EAPOL\\UserEapInfo" #define cwszDefault L"Default" BYTE g_bDefaultSSID[MAX_SSID_LEN]={0x11, 0x22, 0x33, 0x11, 0x22, 0x33, 0x11, 0x22, 0x33, 0x11, 0x22, 0x33, 0x11, 0x22, 0x33, 0x11, 0x22, 0x33, 0x11, 0x22, 0x33, 0x11, 0x22, 0x33, 0x11, 0x22, 0x33, 0x11, 0x22, 0x33, 0x11, 0x22}; // // EAP configuration manipulation routines // //+--------------------------------------------------------------------------- // // Returns a created, empty EAPCFG descriptor node, or NULL on error. // DTLNODE* CreateEapcfgNode ( void ) { DTLNODE* pNode = NULL; EAPCFG* pEapcfg = NULL; pNode = DtlCreateSizedNode( sizeof(EAPCFG), 0L ); if (pNode) { pEapcfg = (EAPCFG* )DtlGetData( pNode ); if (pEapcfg) { pEapcfg->dwKey = (DWORD )-1; pEapcfg->pszConfigDll = NULL; pEapcfg->pszIdentityDll = NULL; pEapcfg->dwStdCredentialFlags = 0; pEapcfg->fProvidesMppeKeys = FALSE; pEapcfg->fForceConfig = FALSE; pEapcfg->pData = NULL; pEapcfg->cbData = 0; pEapcfg->fConfigDllCalled = FALSE; } } return pNode; } //+--------------------------------------------------------------------------- // // Release resources associated with EAPCFG node 'pNode'. See // DtlDestroyList. // VOID DestroyEapcfgNode ( IN OUT DTLNODE* pNode ) { EAPCFG* pEapcfg = NULL; pEapcfg = (EAPCFG* )DtlGetData( pNode ); if (pEapcfg) { if (pEapcfg->pszConfigDll) FREE ( pEapcfg->pszConfigDll ); if (pEapcfg->pszIdentityDll) FREE ( pEapcfg->pszIdentityDll ); if (pEapcfg->pData) FREE ( pEapcfg->pData ); if (pEapcfg->pszFriendlyName) FREE ( pEapcfg->pszFriendlyName ); } DtlDestroyNode( pNode ); } //+--------------------------------------------------------------------------- // // Returns the EAPCFG node in list 'pList' with EAP key value of 'dwKey' // or NULL if not found. // DTLNODE* EapcfgNodeFromKey( IN DTLLIST* pList, IN DWORD dwKey ) { DTLNODE* pNode = NULL; for (pNode = DtlGetFirstNode( pList ); pNode; pNode = DtlGetNextNode( pNode )) { EAPCFG* pEapcfg = (EAPCFG* )DtlGetData( pNode ); if (pEapcfg) { if (pEapcfg->dwKey == dwKey) { return pNode; } } } return NULL; } //+--------------------------------------------------------------------------- // // Returns the address of a created list of installed custom // authentication packages or NULL if none could be read. On success, it // is caller's responsibility to eventually call DtlDestroyList on the // returned list. // DTLLIST* ReadEapcfgList ( IN DWORD dwFlags ) { DWORD dwErr = 0; BOOL fOk = FALSE; DTLLIST* pList = NULL; DTLNODE* pNode = NULL; EAPCFG* pEapcfg = NULL; HKEY hkeyLM = NULL; HKEY hkeyEap = NULL; HKEY hkeyPackage = NULL; CHAR szEapType[ 11 + 1 ]; DWORD dwEapType = 0; TCHAR* psz = NULL; DWORD dw; DWORD cb; INT i; TCHAR* szCLSID = NULL; DWORD dwHidePEAPMSCHAPv2 = 0; HRESULT hr = S_OK; pList = DtlCreateList( 0L ); if (!pList) { return NULL; } // Open the EAP key which contains a sub-key for each installed package. dwErr = RegOpenKeyEx( HKEY_LOCAL_MACHINE, (LPCTSTR)REGKEY_Eap, 0, KEY_READ, &hkeyEap ); if (dwErr != NO_ERROR) { return pList; } // Display EAP-MSCHAPv2 as an EAP method? GetRegDword( hkeyEap, RAS_EAP_VALUENAME_HIDEPEAPMSCHAPv2, &dwHidePEAPMSCHAPv2 ); // Open each sub-key and extract the package definition from it's values. // Problems with opening individual sub-keys result in that node only // being discarded. for (i = 0; TRUE; ++i) { cb = sizeof(szEapType); dwErr = RegEnumKeyExA( hkeyEap, i, szEapType, &cb, NULL, NULL, NULL, NULL ); if (dwErr != 0) { // Includes "out of items", the normal loop termination. break; } dwEapType = atol (szEapType); if (dwHidePEAPMSCHAPv2 != 0) { if (dwEapType == EAP_TYPE_MSCHAPv2) { // ignore EAP-MSCHAPv2 continue; } } // Ignored non-mutual-auth DLLs like EAP if (dwFlags & EAPOL_MUTUAL_AUTH_EAP_ONLY) { if (dwEapType == EAP_TYPE_MD5) { continue; } } dwErr = RegOpenKeyExA( hkeyEap, szEapType, 0, KEY_READ, &hkeyPackage ); if (dwErr != 0) { continue; } do { // Roles Supported dw = RAS_EAP_ROLE_AUTHENTICATEE; GetRegDword( hkeyPackage, RAS_EAP_VALUENAME_ROLES_SUPPORTED, &dw ); if (!(dw & RAS_EAP_ROLE_AUTHENTICATEE)) { break; } if (dw & RAS_EAP_ROLE_EXCLUDE_IN_EAP) { break; } pNode = CreateEapcfgNode(); if (!pNode) { break; } pEapcfg = (EAPCFG* )DtlGetData( pNode ); if (!pEapcfg) { break; } // EAP type ID. pEapcfg->dwKey = (LONG )atol( szEapType ); // Friendly display name. psz = NULL; dwErr = GetRegSz( hkeyPackage, REGVAL_szFriendlyName, &psz ); if (dwErr != 0) { break; } pEapcfg->pszFriendlyName = psz; // Configuration DLL path. psz = NULL; dwErr = GetRegExpandSz( hkeyPackage, REGVAL_szConfigDll, &psz ); if (dwErr != 0) { break; } if (*psz) { pEapcfg->pszConfigDll = psz; } else { FREE ( psz ); } // Identity DLL path. psz = NULL; dwErr = GetRegExpandSz( hkeyPackage, REGVAL_szIdentityDll, &psz ); if (dwErr != 0) { break; } if (*psz) { pEapcfg->pszIdentityDll = psz; } else { FREE ( psz ); } // Prompt user name dw = 1; GetRegDword( hkeyPackage, REGVAL_fRequireUser, &dw ); if (dw) pEapcfg->dwStdCredentialFlags |= EAPCFG_FLAG_RequireUsername; // Prompt password dw = 0; GetRegDword( hkeyPackage, REGVAL_fRequirePwd, &dw ); if (dw) pEapcfg->dwStdCredentialFlags |= EAPCFG_FLAG_RequirePassword; // MPPE encryption keys flag. dw = 0; GetRegDword( hkeyPackage, REGVAL_fMppeSupported, &dw ); pEapcfg->fProvidesMppeKeys = !!dw; // Force configuration API to run at least once. dw = FALSE; GetRegDword( hkeyPackage, REGVAL_fForceConfig, &dw ); pEapcfg->fForceConfig = !!dw; // Configuration blob. GetRegBinary( hkeyPackage, REGVAL_pData, &pEapcfg->pData, &pEapcfg->cbData ); // ConfigCLSID dwErr = GetRegSz( hkeyPackage, RAS_EAP_VALUENAME_CONFIG_CLSID, &szCLSID ); if (dwErr != 0) { break; } // Ignore errors. Eg. EAP MD5-Challenge does not have a ConfigCLSID. // // hr = CLSIDFromString( szCLSID, &( pEapcfg->guidConfigCLSID ) ); FREE ( szCLSID ); // Add the completed node to the list. DtlAddNodeLast( pList, pNode ); fOk = TRUE; } while (FALSE); if (!fOk && pNode) { DestroyEapcfgNode( pNode ); } RegCloseKey( hkeyPackage ); } RegCloseKey( hkeyEap ); return pList; } //+--------------------------------------------------------------------------- // // Allocates a sized node with space for 'lDataBytes' bytes of user data // built-in. The node is initialized to contain the address of the // built-in user data block (or NULL if of zero length) and the // user-defined node identification code 'lNodeId'. The user data block // is zeroed. // // Returns the address of the new node or NULL if out of memory. // DTLNODE* DtlCreateSizedNode ( IN LONG lDataBytes, IN LONG_PTR lNodeId ) { DTLNODE* pdtlnode = (DTLNODE *) MALLOC ( sizeof(DTLNODE) + lDataBytes ); if (pdtlnode) { ZeroMemory( pdtlnode, sizeof(DTLNODE) + lDataBytes ); if (lDataBytes) pdtlnode->pData = pdtlnode + 1; pdtlnode->lNodeId = lNodeId; } return pdtlnode; } //+--------------------------------------------------------------------------- // // Deallocates node 'pdtlnode'. It is the caller's responsibility to free // the entry in an unsized node, if necessary. // VOID DtlDestroyNode ( IN OUT DTLNODE* pdtlnode ) { if (pdtlnode != NULL) { FREE ( pdtlnode ); } } //+--------------------------------------------------------------------------- // // Adds 'pdtlnode' at the end of list 'pdtllist'. // // Returns the address of the added node, i.e. 'pdtlnode'. // DTLNODE* DtlAddNodeLast ( IN OUT DTLLIST* pdtllist, IN OUT DTLNODE* pdtlnode ) { if (pdtllist->lNodes) { pdtlnode->pdtlnodePrev = pdtllist->pdtlnodeLast; pdtllist->pdtlnodeLast->pdtlnodeNext = pdtlnode; } else { pdtlnode->pdtlnodePrev = NULL; pdtllist->pdtlnodeFirst = pdtlnode; } pdtllist->pdtlnodeLast = pdtlnode; pdtlnode->pdtlnodeNext = NULL; ++pdtllist->lNodes; return pdtlnode; } //+--------------------------------------------------------------------------- // // Removes node 'pdtlnodeInList' from list 'pdtllist'. // // Returns the address of the removed node, i.e. 'pdtlnodeInList'. // DTLNODE* DtlRemoveNode ( IN OUT DTLLIST* pdtllist, IN OUT DTLNODE* pdtlnodeInList ) { if (pdtlnodeInList->pdtlnodePrev) pdtlnodeInList->pdtlnodePrev->pdtlnodeNext = pdtlnodeInList->pdtlnodeNext; else pdtllist->pdtlnodeFirst = pdtlnodeInList->pdtlnodeNext; if (pdtlnodeInList->pdtlnodeNext) pdtlnodeInList->pdtlnodeNext->pdtlnodePrev = pdtlnodeInList->pdtlnodePrev; else pdtllist->pdtlnodeLast = pdtlnodeInList->pdtlnodePrev; --pdtllist->lNodes; return pdtlnodeInList; } //+--------------------------------------------------------------------------- // // Allocates a list and initializes it to empty. The list is marked with // the user-defined list identification code 'lListId'. // // Returns the address of the list control block or NULL if out of memory. // DTLLIST* DtlCreateList ( IN LONG lListId ) { DTLLIST* pdtllist = MALLOC (sizeof(DTLLIST)); if (pdtllist) { pdtllist->pdtlnodeFirst = NULL; pdtllist->pdtlnodeLast = NULL; pdtllist->lNodes = 0; pdtllist->lListId = lListId; } return pdtllist; } //+--------------------------------------------------------------------------- // // Deallocates all nodes in list 'pdtllist' using the node deallocation // function 'pfuncDestroyNode' if non-NULL or DtlDestroyNode otherwise. // Won't GP-fault if passed a NULL list, e.g. if 'pdtllist', was never // allocated. // VOID DtlDestroyList ( IN OUT DTLLIST* pdtllist, IN PDESTROYNODE pfuncDestroyNode ) { if (pdtllist) { DTLNODE* pdtlnode; while (pdtlnode = DtlGetFirstNode( pdtllist )) { DtlRemoveNode( pdtllist, pdtlnode ); if (pfuncDestroyNode) pfuncDestroyNode( pdtlnode ); else DtlDestroyNode( pdtlnode ); } FREE ( pdtllist ); } } //+--------------------------------------------------------------------------- // // Set '*ppbResult' to the BINARY registry value 'pszName' under key // 'hkey'. If the value does not exist *ppbResult' is set to NULL. // '*PcbResult' is the number of bytes in the returned '*ppbResult'. It // is caller's responsibility to Free the returned block. // VOID GetRegBinary ( IN HKEY hkey, IN TCHAR* pszName, OUT BYTE** ppbResult, OUT DWORD* pcbResult ) { DWORD dwErr; DWORD dwType; BYTE* pb; DWORD cb; *ppbResult = NULL; *pcbResult = 0; // Get result buffer size required. dwErr = RegQueryValueEx( hkey, pszName, NULL, &dwType, NULL, &cb ); if (dwErr != NO_ERROR) { return; } // Allocate result buffer. pb = MALLOC (cb); if (!pb) { return; } // Get the result block. dwErr = RegQueryValueEx( hkey, pszName, NULL, &dwType, (LPBYTE )pb, &cb ); if (dwErr == NO_ERROR) { *ppbResult = pb; *pcbResult = cb; } } //+--------------------------------------------------------------------------- // // Set '*pdwResult' to the DWORD registry value 'pszName' under key // 'hkey'. If the value does not exist '*pdwResult' is unchanged. // VOID GetRegDword ( IN HKEY hkey, IN TCHAR* pszName, OUT DWORD* pdwResult ) { DWORD dwErr; DWORD dwType; DWORD dwResult; DWORD cb; cb = sizeof(DWORD); dwErr = RegQueryValueEx( hkey, pszName, NULL, &dwType, (LPBYTE )&dwResult, &cb ); if ((dwErr == NO_ERROR) && dwType == REG_DWORD && cb == sizeof(DWORD)) { *pdwResult = dwResult; } } //+--------------------------------------------------------------------------- // // Set '*ppszResult' to the fully expanded EXPAND_SZ registry value // 'pszName' under key 'hkey'. If the value does not exist *ppszResult' // is set to empty string. // // Returns 0 if successful or an error code. It is caller's // responsibility to Free the returned string. // DWORD GetRegExpandSz( IN HKEY hkey, IN TCHAR* pszName, OUT TCHAR** ppszResult ) { DWORD dwErr; DWORD cb; TCHAR* pszResult; // Get the unexpanded result string. // dwErr = GetRegSz( hkey, pszName, ppszResult ); if (dwErr != 0) { return dwErr; } // Find out how big the expanded string will be. // cb = ExpandEnvironmentStrings( *ppszResult, NULL, 0 ); if (cb == 0) { dwErr = GetLastError(); FREE ( *ppszResult ); return dwErr; } // Allocate a buffer for the expanded string. // pszResult = MALLOC ((cb + 1) * sizeof(TCHAR)); if (!pszResult) { return ERROR_NOT_ENOUGH_MEMORY; } // Expand the environmant variables in the string, storing the result in // the allocated buffer. // cb = ExpandEnvironmentStrings( *ppszResult, pszResult, cb + 1 ); if (cb == 0) { dwErr = GetLastError(); if (*ppszResult != NULL) { FREE ( *ppszResult ); } if (pszResult != NULL) { FREE ( pszResult ); } return dwErr; } FREE ( *ppszResult ); *ppszResult = pszResult; return 0; } //+--------------------------------------------------------------------------- // // Set '*ppszResult' to the SZ registry value 'pszName' under key 'hkey'. // If the value does not exist *ppszResult' is set to empty string. // // Returns 0 if successful or an error code. It is caller's // responsibility to Free the returned string. // DWORD GetRegSz( IN HKEY hkey, IN TCHAR* pszName, OUT TCHAR** ppszResult ) { DWORD dwErr = NO_ERROR; DWORD dwType; DWORD cb; TCHAR* pszResult; // Get result buffer size required. dwErr = RegQueryValueEx( hkey, pszName, NULL, &dwType, NULL, &cb ); if (dwErr != NO_ERROR) { cb = sizeof(TCHAR); } // Allocate result buffer. pszResult = MALLOC (cb * sizeof(TCHAR)); if (!pszResult) { return ERROR_NOT_ENOUGH_MEMORY; } *pszResult = TEXT('\0'); *ppszResult = pszResult; // Get the result string. It's not an error if we can't get it. dwErr = RegQueryValueEx( hkey, pszName, NULL, &dwType, (LPBYTE )pszResult, &cb ); return NO_ERROR; } // // WZCGetEapUserInfo // // Description: // // Function called to retrieve the user data for an interface for a // specific EAP type and SSID (if any). Data is retrieved from the HKCU hive // // Arguments: // pwszGUID - pointer to GUID string for the interface // dwEapTypeId - EAP type for which user data is to be stored // dwSizeOfSSID - Size of Special identifier if any for the EAP user blob // pbSSID - Special identifier if any for the EAP user blob // pbUserInfo - output: pointer to EAP user data blob // dwInfoSize - output: pointer to size of EAP user blob // // Return values: // NO_ERROR - success // non-zero - error // DWORD WZCGetEapUserInfo ( IN WCHAR *pwszGUID, IN DWORD dwEapTypeId, IN DWORD dwSizeOfSSID, IN BYTE *pbSSID, IN OUT PBYTE pbUserInfo, IN OUT DWORD *pdwInfoSize ) { HKEY hkey = NULL; HKEY hkey1 = NULL; HKEY hkey2 = NULL; DWORD dwNumValues = 0, dwMaxValueNameLen = 0, dwTempValueNameLen = 0, dwMaxValueLen = 0; DWORD dwIndex = 0, dwMaxValueName = 0; WCHAR *pwszValueName = NULL; BYTE *pbValueBuf = NULL; DWORD dwValueData = 0; BYTE *pbDefaultValue = NULL; DWORD dwDefaultValueLen = 0; BYTE *pbEapBlob = NULL; DWORD dwEapBlob = 0; BYTE *pbAuthData = NULL; DWORD dwAuthData = 0; BOOLEAN fFoundValue = FALSE; EAPOL_INTF_PARAMS *pRegParams = NULL; LONG lError = ERROR_SUCCESS; DWORD dwRetCode = ERROR_SUCCESS; do { // Validate input params if (pwszGUID == NULL) { dwRetCode = ERROR_CAN_NOT_COMPLETE; break; } if (dwEapTypeId == 0) { dwRetCode = ERROR_CAN_NOT_COMPLETE; break; } // Get handle to HKCU\Software\...\UserEapInfo if ((lError = RegOpenKeyEx ( HKEY_CURRENT_USER, cwszEapKeyEapolUser, 0, KEY_READ, &hkey1 )) != ERROR_SUCCESS) { dwRetCode = (DWORD)lError; break; } // Get handle to HKCU\Software\...\UserEapInfo\ if ((lError = RegOpenKeyEx ( hkey1, pwszGUID, 0, KEY_READ, &hkey2 )) != ERROR_SUCCESS) { dwRetCode = (DWORD)lError; break; } // Set correct SSID if (dwSizeOfSSID == 0) { pbSSID = g_bDefaultSSID; dwSizeOfSSID = MAX_SSID_LEN; } if ((lError = RegQueryInfoKey ( hkey2, NULL, NULL, NULL, NULL, NULL, NULL, &dwNumValues, &dwMaxValueNameLen, &dwMaxValueLen, NULL, NULL )) != NO_ERROR) { dwRetCode = (DWORD)lError; break; } if ((pwszValueName = MALLOC ((dwMaxValueNameLen + 1) * sizeof (WCHAR))) == NULL) { dwRetCode = ERROR_NOT_ENOUGH_MEMORY; break; } dwMaxValueNameLen++; if ((pbValueBuf = MALLOC (dwMaxValueLen)) == NULL) { dwRetCode = ERROR_NOT_ENOUGH_MEMORY; break; } for (dwIndex = 0; dwIndex < dwNumValues; dwIndex++) { dwValueData = dwMaxValueLen; dwTempValueNameLen = dwMaxValueNameLen; if ((lError = RegEnumValue ( hkey2, dwIndex, pwszValueName, &dwTempValueNameLen, NULL, NULL, pbValueBuf, &dwValueData )) != ERROR_SUCCESS) { if (lError != ERROR_MORE_DATA) { break; } lError = ERROR_SUCCESS; } if (dwValueData < sizeof (EAPOL_INTF_PARAMS)) { lError = ERROR_INVALID_DATA; break; } pRegParams = (EAPOL_INTF_PARAMS *)pbValueBuf; if (((DWORD)_wtol(pwszValueName)) > dwMaxValueName) { dwMaxValueName = _wtol (pwszValueName); } if (!memcmp (pRegParams->bSSID, pbSSID, dwSizeOfSSID)) { fFoundValue = TRUE; break; } } if ((lError != ERROR_SUCCESS) && (lError != ERROR_NO_MORE_ITEMS)) { dwRetCode = (DWORD)lError; break; } else { lError = ERROR_SUCCESS; } if (!fFoundValue) { pbEapBlob = NULL; dwEapBlob = 0; } else { // Use pbValueBuf & dwValueData pbEapBlob = pbValueBuf; dwEapBlob = dwValueData; } // If default blob is not present, exit if ((pbEapBlob == NULL) && (dwEapBlob == 0)) { *pdwInfoSize = 0; break; } if ((dwRetCode = WZCGetEapData ( dwEapTypeId, dwEapBlob, pbEapBlob, sizeof (EAPOL_INTF_PARAMS), &dwAuthData, &pbAuthData )) != NO_ERROR) { break; } // Return the data if sufficient space allocated if ((pbUserInfo != NULL) && (*pdwInfoSize >= dwAuthData)) { memcpy (pbUserInfo, pbAuthData, dwAuthData); } else { dwRetCode = ERROR_INSUFFICIENT_BUFFER; } *pdwInfoSize = dwAuthData; } while (FALSE); if (hkey != NULL) { RegCloseKey (hkey); } if (hkey1 != NULL) { RegCloseKey (hkey1); } if (hkey2 != NULL) { RegCloseKey (hkey2); } if (pbValueBuf != NULL) { FREE (pbValueBuf); } if (pbDefaultValue != NULL) { FREE (pbDefaultValue); } if (pwszValueName != NULL) { FREE (pwszValueName); } return dwRetCode; } // // WZCGetEapData // // Description: // // Function to extract Eap Data out of a blob containing many EAP data // // Arguments: // dwEapType - // dwSizeOfIn - // pbBufferIn - // dwOffset - // pdwSizeOfOut - // ppbBufferOut - // // Return values: // // DWORD WZCGetEapData ( IN DWORD dwEapType, IN DWORD dwSizeOfIn, IN BYTE *pbBufferIn, IN DWORD dwOffset, IN DWORD *pdwSizeOfOut, IN PBYTE *ppbBufferOut ) { DWORD dwRetCode = NO_ERROR; DWORD cbOffset = 0; EAPOL_AUTH_DATA *pCustomData = NULL; do { *pdwSizeOfOut = 0; *ppbBufferOut = NULL; if (pbBufferIn == NULL) { break; } // Align to start of EAP blob cbOffset = dwOffset; while (cbOffset < dwSizeOfIn) { pCustomData = (EAPOL_AUTH_DATA *) ((PBYTE) pbBufferIn + cbOffset); if (pCustomData->dwEapType == dwEapType) { break; } cbOffset += sizeof (EAPOL_AUTH_DATA) + pCustomData->dwSize; } if (cbOffset < dwSizeOfIn) { *pdwSizeOfOut = pCustomData->dwSize; *ppbBufferOut = pCustomData->bData; } } while (FALSE); return dwRetCode; } // // WZCEapolFreeState // // Description: // // Function to free EAPOL interface state information on the client side // obtained via RPC query // // Arguments: // pIntfState - // // Return values: // // DWORD WZCEapolFreeState ( IN EAPOL_INTF_STATE *pIntfState ) { DWORD dwRetCode = NO_ERROR; do { RpcFree(pIntfState->pwszLocalMACAddr); RpcFree(pIntfState->pwszRemoteMACAddr); RpcFree(pIntfState->pszEapIdentity); } while (FALSE); return dwRetCode; }