EAP-Message integration continues...
This commit is contained in:
parent
e7e1a6735d
commit
2041accecb
@ -81,6 +81,13 @@ namespace eap
|
|||||||
_In_ HANDLE hTokenImpersonateUser,
|
_In_ HANDLE hTokenImpersonateUser,
|
||||||
_In_opt_ DWORD dwMaxSendPacketSize = MAXDWORD);
|
_In_opt_ DWORD dwMaxSendPacketSize = MAXDWORD);
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Ends an EAP authentication session for the EAP method.
|
||||||
|
///
|
||||||
|
/// \sa [EapPeerEndSession function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363604.aspx)
|
||||||
|
///
|
||||||
|
virtual void end_session();
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Processes a packet received by EapHost from a supplicant.
|
/// Processes a packet received by EapHost from a supplicant.
|
||||||
///
|
///
|
||||||
@ -91,15 +98,103 @@ namespace eap
|
|||||||
_In_ DWORD dwReceivedPacketSize,
|
_In_ DWORD dwReceivedPacketSize,
|
||||||
_Out_ EapPeerMethodOutput *pEapOutput);
|
_Out_ EapPeerMethodOutput *pEapOutput);
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Obtains a response packet from the EAP method.
|
||||||
|
///
|
||||||
|
/// \sa [EapPeerGetResponsePacket function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363610.aspx)
|
||||||
|
///
|
||||||
|
virtual void get_response_packet(
|
||||||
|
_Inout_bytecap_(*dwSendPacketSize) void *pSendPacket,
|
||||||
|
_Inout_ DWORD *pdwSendPacketSize);
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Obtains the result of an authentication session from the EAP method.
|
||||||
|
///
|
||||||
|
/// \sa [EapPeerGetResult function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363611.aspx)
|
||||||
|
///
|
||||||
|
virtual void get_result(
|
||||||
|
_In_ EapPeerMethodResultReason reason,
|
||||||
|
_Inout_ EapPeerMethodResult *pResult);
|
||||||
|
|
||||||
|
/// @}
|
||||||
|
|
||||||
|
/// \name User Interaction
|
||||||
|
/// @{
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Obtains the user interface context from the EAP method.
|
||||||
|
///
|
||||||
|
/// \note This function is always followed by the `EapPeerInvokeInteractiveUI()` function, which is followed by the `EapPeerSetUIContext()` function.
|
||||||
|
///
|
||||||
|
/// \sa [EapPeerGetUIContext function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363612.aspx)
|
||||||
|
///
|
||||||
|
virtual void get_ui_context(
|
||||||
|
_Inout_ BYTE **ppUIContextData,
|
||||||
|
_Inout_ DWORD *pdwUIContextDataSize);
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Provides a user interface context to the EAP method.
|
||||||
|
///
|
||||||
|
/// \note This function is called after the UI has been raised through the `EapPeerGetUIContext()` function.
|
||||||
|
///
|
||||||
|
/// \sa [EapPeerSetUIContext function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363626.aspx)
|
||||||
|
///
|
||||||
|
virtual void set_ui_context(
|
||||||
|
_In_count_(dwUIContextDataSize) const BYTE *pUIContextData,
|
||||||
|
_In_ DWORD dwUIContextDataSize,
|
||||||
|
_Out_ EapPeerMethodOutput *pEapOutput);
|
||||||
|
|
||||||
|
/// @}
|
||||||
|
|
||||||
|
/// \name EAP Response Attributes
|
||||||
|
/// @{
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Obtains an array of EAP response attributes from the EAP method.
|
||||||
|
///
|
||||||
|
/// \sa [EapPeerGetResponseAttributes function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363609.aspx)
|
||||||
|
///
|
||||||
|
virtual void get_response_attributes(_Inout_ EapAttributes *pAttribs);
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Provides an updated array of EAP response attributes to the EAP method.
|
||||||
|
///
|
||||||
|
/// \sa [EapPeerSetResponseAttributes function](https://msdn.microsoft.com/en-us/library/windows/desktop/aa363625.aspx)
|
||||||
|
///
|
||||||
|
virtual void set_response_attributes(
|
||||||
|
_In_ const EapAttributes *pAttribs,
|
||||||
|
_Out_ EapPeerMethodOutput *pEapOutput);
|
||||||
|
|
||||||
/// @}
|
/// @}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
credentials_eapmsg &m_cred; ///< Method user credentials
|
///
|
||||||
|
/// Converts EapHost peer action to output structure.
|
||||||
|
///
|
||||||
|
/// \param[in ] action EapHost peer action
|
||||||
|
/// \param[out] pEapOutput EAP method output structure
|
||||||
|
///
|
||||||
|
inline void action_to_output(
|
||||||
|
_In_ EapHostPeerResponseAction action,
|
||||||
|
_Out_ EapPeerMethodOutput *pEapOutput)
|
||||||
|
{
|
||||||
|
switch (action) {
|
||||||
|
case EapHostPeerResponseDiscard : pEapOutput->action = EapPeerMethodResponseActionDiscard ; break;
|
||||||
|
case EapHostPeerResponseSend : pEapOutput->action = EapPeerMethodResponseActionSend ; break;
|
||||||
|
case EapHostPeerResponseResult : pEapOutput->action = EapPeerMethodResponseActionResult ; break;
|
||||||
|
case EapHostPeerResponseInvokeUi : pEapOutput->action = EapPeerMethodResponseActionInvokeUI; break;
|
||||||
|
case EapHostPeerResponseRespond : pEapOutput->action = EapPeerMethodResponseActionRespond ; break;
|
||||||
|
case EapHostPeerResponseStartAuthentication: pEapOutput->action = EapPeerMethodResponseActionDiscard ; break; // The session could not be found. So the supplicant either needs to start session again with the same packet or discard the packet.
|
||||||
|
case EapHostPeerResponseNone : pEapOutput->action = EapPeerMethodResponseActionNone ; break;
|
||||||
|
default : throw std::invalid_argument(winstd::string_printf(__FUNCTION__ " Unknown action (%u).", action).c_str());
|
||||||
|
}
|
||||||
|
pEapOutput->fAllowNotifications = TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
enum {
|
protected:
|
||||||
phase_unknown = -1, ///< Unknown phase
|
EAP_SESSIONID m_session_id; ///< EAP session ID
|
||||||
phase_init = 0, ///< Handshake initialize
|
|
||||||
phase_finished, ///< Connection shut down
|
sanitizing_blob m_ctx_req_blob; ///< Inner UI context request
|
||||||
} m_phase; ///< What phase is our communication at?
|
sanitizing_blob m_ctx_res_blob; ///< Inner UI context response
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -29,16 +29,14 @@ using namespace winstd;
|
|||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
eap::method_eapmsg::method_eapmsg(_In_ module &module, _In_ config_method_eapmsg &cfg, _In_ credentials_eapmsg &cred) :
|
eap::method_eapmsg::method_eapmsg(_In_ module &module, _In_ config_method_eapmsg &cfg, _In_ credentials_eapmsg &cred) :
|
||||||
m_cred(cred),
|
m_session_id(0),
|
||||||
m_phase(phase_unknown),
|
|
||||||
method_noneap(module, cfg, cred)
|
method_noneap(module, cfg, cred)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
eap::method_eapmsg::method_eapmsg(_Inout_ method_eapmsg &&other) :
|
eap::method_eapmsg::method_eapmsg(_Inout_ method_eapmsg &&other) :
|
||||||
m_cred ( other.m_cred ),
|
m_session_id (std::move(other.m_session_id)),
|
||||||
m_phase (std::move(other.m_phase )),
|
|
||||||
method_noneap(std::move(other ))
|
method_noneap(std::move(other ))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@ -47,9 +45,8 @@ eap::method_eapmsg::method_eapmsg(_Inout_ method_eapmsg &&other) :
|
|||||||
eap::method_eapmsg& eap::method_eapmsg::operator=(_Inout_ method_eapmsg &&other)
|
eap::method_eapmsg& eap::method_eapmsg::operator=(_Inout_ method_eapmsg &&other)
|
||||||
{
|
{
|
||||||
if (this != std::addressof(other)) {
|
if (this != std::addressof(other)) {
|
||||||
assert(std::addressof(m_cred) == std::addressof(other.m_cred)); // Move method with same credentials only!
|
|
||||||
(method_noneap&)*this = std::move(other );
|
(method_noneap&)*this = std::move(other );
|
||||||
m_phase = std::move(other.m_phase );
|
m_session_id = std::move(other.m_session_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return *this;
|
return *this;
|
||||||
@ -62,10 +59,46 @@ void eap::method_eapmsg::begin_session(
|
|||||||
_In_ HANDLE hTokenImpersonateUser,
|
_In_ HANDLE hTokenImpersonateUser,
|
||||||
_In_opt_ DWORD dwMaxSendPacketSize)
|
_In_opt_ DWORD dwMaxSendPacketSize)
|
||||||
{
|
{
|
||||||
method_noneap::begin_session(dwFlags, pAttributeArray, hTokenImpersonateUser, dwMaxSendPacketSize);
|
// Create EapHost peer session using available connection data (m_cfg) and user data (m_cred).
|
||||||
|
auto &cfg = dynamic_cast<config_method_eapmsg&>(m_cfg);
|
||||||
|
auto &cred = dynamic_cast<credentials_eapmsg &>(m_cred);
|
||||||
|
eap_error_runtime error;
|
||||||
|
DWORD dwResult = EapHostPeerBeginSession(
|
||||||
|
dwFlags,
|
||||||
|
cfg.m_type,
|
||||||
|
pAttributeArray,
|
||||||
|
hTokenImpersonateUser,
|
||||||
|
(DWORD)cfg.m_cfg_blob.size(),
|
||||||
|
cfg.m_cfg_blob.data(),
|
||||||
|
(DWORD)cred.m_cred_blob.size(),
|
||||||
|
cred.m_cred_blob.data(),
|
||||||
|
dwMaxSendPacketSize,
|
||||||
|
NULL, NULL, NULL,
|
||||||
|
&m_session_id,
|
||||||
|
&error._Myptr);
|
||||||
|
if (dwResult == ERROR_SUCCESS) {
|
||||||
|
// Session succesfully created.
|
||||||
|
method_noneap::begin_session(dwFlags, pAttributeArray, hTokenImpersonateUser, dwMaxSendPacketSize);
|
||||||
|
|
||||||
m_module.log_event(&EAPMETHOD_METHOD_HANDSHAKE_START2, event_data((unsigned int)m_cfg.get_method_id()), event_data::blank);
|
m_module.log_event(&EAPMETHOD_METHOD_HANDSHAKE_START2, event_data((unsigned int)m_cfg.get_method_id()), event_data::blank);
|
||||||
m_phase = phase_init;
|
} else if (error)
|
||||||
|
throw eap_runtime_error(*error , __FUNCTION__ " EapHostPeerBeginSession failed.");
|
||||||
|
else
|
||||||
|
throw win_runtime_error(dwResult, __FUNCTION__ " EapHostPeerBeginSession failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void eap::method_eapmsg::end_session()
|
||||||
|
{
|
||||||
|
// End EapHost peer session.
|
||||||
|
eap_error_runtime error;
|
||||||
|
DWORD dwResult = EapHostPeerEndSession(m_session_id, &error._Myptr);
|
||||||
|
if (dwResult == ERROR_SUCCESS) {
|
||||||
|
// Session successfuly ended.
|
||||||
|
} else if (error)
|
||||||
|
throw eap_runtime_error(*error , __FUNCTION__ " EapHostPeerEndSession failed.");
|
||||||
|
else
|
||||||
|
throw win_runtime_error(dwResult, __FUNCTION__ " EapHostPeerEndSession failed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -74,39 +107,171 @@ void eap::method_eapmsg::process_request_packet(
|
|||||||
_In_ DWORD dwReceivedPacketSize,
|
_In_ DWORD dwReceivedPacketSize,
|
||||||
_Out_ EapPeerMethodOutput *pEapOutput)
|
_Out_ EapPeerMethodOutput *pEapOutput)
|
||||||
{
|
{
|
||||||
UNREFERENCED_PARAMETER(pReceivedPacket);
|
|
||||||
assert(pReceivedPacket || dwReceivedPacketSize == 0);
|
assert(pReceivedPacket || dwReceivedPacketSize == 0);
|
||||||
assert(pEapOutput);
|
assert(pEapOutput);
|
||||||
|
|
||||||
m_module.log_event(&EAPMETHOD_PACKET_RECV, event_data((unsigned int)m_cfg.get_method_id()), event_data((unsigned int)dwReceivedPacketSize), event_data::blank);
|
m_module.log_event(&EAPMETHOD_PACKET_RECV, event_data((unsigned int)m_cfg.get_method_id()), event_data((unsigned int)dwReceivedPacketSize), event_data::blank);
|
||||||
|
|
||||||
// TODO: Finish!
|
// Let EapHost peer process the packet.
|
||||||
//switch (m_phase) {
|
EapHostPeerResponseAction action;
|
||||||
//case phase_init: {
|
eap_error_runtime error;
|
||||||
// // Convert username and password to UTF-8.
|
DWORD dwResult = EapHostPeerProcessReceivedPacket(
|
||||||
// sanitizing_string identity_utf8, password_utf8;
|
m_session_id,
|
||||||
// WideCharToMultiByte(CP_UTF8, 0, m_cred.m_identity.c_str(), (int)m_cred.m_identity.length(), identity_utf8, NULL, NULL);
|
dwReceivedPacketSize,
|
||||||
// WideCharToMultiByte(CP_UTF8, 0, m_cred.m_password.c_str(), (int)m_cred.m_password.length(), password_utf8, NULL, NULL);
|
reinterpret_cast<const BYTE*>(pReceivedPacket),
|
||||||
|
&action,
|
||||||
// // EAPMsg passwords must be padded to 16B boundary according to RFC 5281. Will not add random extra padding here, as length obfuscation should be done by outer transport layers.
|
&error._Myptr);
|
||||||
// size_t padding_password_ex = (16 - password_utf8.length()) % 16;
|
if (dwResult == ERROR_SUCCESS) {
|
||||||
// password_utf8.append(padding_password_ex, 0);
|
// Packet successfuly processed.
|
||||||
|
action_to_output(action, pEapOutput);
|
||||||
// m_packet_res.clear();
|
} else if (error)
|
||||||
|
throw eap_runtime_error(*error , __FUNCTION__ " EapHostPeerProcessReceivedPacket failed.");
|
||||||
// // Diameter AVP (User-Name=1, User-Password=2)
|
else
|
||||||
// append_avp(1, diameter_avp_flag_mandatory, identity_utf8.data(), (unsigned int)identity_utf8.size());
|
throw win_runtime_error(dwResult, __FUNCTION__ " EapHostPeerProcessReceivedPacket failed.");
|
||||||
// append_avp(2, diameter_avp_flag_mandatory, password_utf8.data(), (unsigned int)password_utf8.size());
|
}
|
||||||
|
|
||||||
// m_phase = phase_finished;
|
|
||||||
// m_cfg.m_last_status = config_method::status_cred_invalid; // Blame credentials if we fail beyond this point.
|
void eap::method_eapmsg::get_response_packet(
|
||||||
// break;
|
_Inout_bytecap_(*dwSendPacketSize) void *pSendPacket,
|
||||||
//}
|
_Inout_ DWORD *pdwSendPacketSize)
|
||||||
|
{
|
||||||
//case phase_finished:
|
assert(pdwSendPacketSize);
|
||||||
// break;
|
assert(pSendPacket || !*pdwSendPacketSize);
|
||||||
//}
|
|
||||||
|
// Let EapHost peer prepare response packet.
|
||||||
pEapOutput->fAllowNotifications = TRUE;
|
DWORD size_max = *pdwSendPacketSize;
|
||||||
pEapOutput->action = EapPeerMethodResponseActionSend;
|
eap_blob_runtime packet;
|
||||||
|
eap_error_runtime error;
|
||||||
|
DWORD dwResult = EapHostPeerGetSendPacket(
|
||||||
|
m_session_id,
|
||||||
|
pdwSendPacketSize,
|
||||||
|
&packet._Myptr,
|
||||||
|
&error._Myptr);
|
||||||
|
if (dwResult == ERROR_SUCCESS) {
|
||||||
|
// Packet successfuly prepared.
|
||||||
|
memcpy_s(pSendPacket, size_max, packet.get(), *pdwSendPacketSize);
|
||||||
|
} else if (error)
|
||||||
|
throw eap_runtime_error(*error , __FUNCTION__ " EapHostPeerGetSendPacket failed.");
|
||||||
|
else
|
||||||
|
throw win_runtime_error(dwResult, __FUNCTION__ " EapHostPeerGetSendPacket failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void eap::method_eapmsg::get_result(
|
||||||
|
_In_ EapPeerMethodResultReason reason,
|
||||||
|
_Inout_ EapPeerMethodResult *pResult)
|
||||||
|
{
|
||||||
|
assert(pResult);
|
||||||
|
|
||||||
|
if (reason == EapPeerMethodResultSuccess) {
|
||||||
|
// Let EapHost peer return result.
|
||||||
|
eap_error_runtime error;
|
||||||
|
EapHostPeerMethodResult result = {};
|
||||||
|
DWORD dwResult = EapHostPeerGetResult(
|
||||||
|
m_session_id,
|
||||||
|
EapHostPeerMethodResultFromMethod,
|
||||||
|
&result,
|
||||||
|
&error._Myptr);
|
||||||
|
if (dwResult == ERROR_SUCCESS) {
|
||||||
|
// Result successfuly returned.
|
||||||
|
pResult->fIsSuccess = result.fIsSuccess;
|
||||||
|
pResult->dwFailureReasonCode = result.dwFailureReasonCode;
|
||||||
|
pResult->pAttribArray = result.pAttribArray;
|
||||||
|
pResult->pEapError = result.pEapError;
|
||||||
|
|
||||||
|
if (result.fSaveConnectionData)
|
||||||
|
dynamic_cast<config_method_eapmsg&>(m_cfg).m_cfg_blob.assign(result.pConnectionData, result.pConnectionData + result.dwSizeofConnectionData);
|
||||||
|
|
||||||
|
if (result.fSaveUserData)
|
||||||
|
dynamic_cast<credentials_eapmsg &>(m_cred).m_cred_blob.assign(result.pUserData, result.pUserData + result.dwSizeofUserData);
|
||||||
|
} else if (error)
|
||||||
|
throw eap_runtime_error(*error , __FUNCTION__ " EapHostPeerGetResult failed.");
|
||||||
|
else
|
||||||
|
throw win_runtime_error(dwResult, __FUNCTION__ " EapHostPeerGetResult failed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void eap::method_eapmsg::get_ui_context(
|
||||||
|
_Inout_ BYTE **ppUIContextData,
|
||||||
|
_Inout_ DWORD *pdwUIContextDataSize)
|
||||||
|
{
|
||||||
|
// Get EapHost peer UI context data.
|
||||||
|
eap_error_runtime error;
|
||||||
|
DWORD dwResult = EapHostPeerGetUIContext(
|
||||||
|
m_session_id,
|
||||||
|
pdwUIContextDataSize,
|
||||||
|
ppUIContextData,
|
||||||
|
&error._Myptr);
|
||||||
|
if (dwResult == ERROR_SUCCESS) {
|
||||||
|
// UI context data successfuly returned.
|
||||||
|
} else if (error)
|
||||||
|
throw eap_runtime_error(*error , __FUNCTION__ " EapHostPeerGetUIContext failed.");
|
||||||
|
else
|
||||||
|
throw win_runtime_error(dwResult, __FUNCTION__ " EapHostPeerGetUIContext failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void eap::method_eapmsg::set_ui_context(
|
||||||
|
_In_count_(dwUIContextDataSize) const BYTE *pUIContextData,
|
||||||
|
_In_ DWORD dwUIContextDataSize,
|
||||||
|
_Out_ EapPeerMethodOutput *pEapOutput)
|
||||||
|
{
|
||||||
|
assert(pEapOutput);
|
||||||
|
|
||||||
|
// Set EapHost peer UI context data.
|
||||||
|
EapHostPeerResponseAction action;
|
||||||
|
eap_error_runtime error;
|
||||||
|
DWORD dwResult = EapHostPeerSetUIContext(
|
||||||
|
m_session_id,
|
||||||
|
dwUIContextDataSize,
|
||||||
|
pUIContextData,
|
||||||
|
&action,
|
||||||
|
&error._Myptr);
|
||||||
|
if (dwResult == ERROR_SUCCESS) {
|
||||||
|
// UI context data successfuly returned.
|
||||||
|
action_to_output(action, pEapOutput);
|
||||||
|
} else if (error)
|
||||||
|
throw eap_runtime_error(*error , __FUNCTION__ " EapHostPeerSetUIContext failed.");
|
||||||
|
else
|
||||||
|
throw win_runtime_error(dwResult, __FUNCTION__ " EapHostPeerSetUIContext failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void eap::method_eapmsg::get_response_attributes(_Inout_ EapAttributes *pAttribs)
|
||||||
|
{
|
||||||
|
// Get response attributes from EapHost peer.
|
||||||
|
eap_error_runtime error;
|
||||||
|
DWORD dwResult = EapHostPeerGetResponseAttributes(
|
||||||
|
m_session_id,
|
||||||
|
pAttribs,
|
||||||
|
&error._Myptr);
|
||||||
|
if (dwResult == ERROR_SUCCESS) {
|
||||||
|
// Response attributes successfuly returned.
|
||||||
|
} else if (error)
|
||||||
|
throw eap_runtime_error(*error , __FUNCTION__ " EapHostPeerGetResponseAttributes failed.");
|
||||||
|
else
|
||||||
|
throw win_runtime_error(dwResult, __FUNCTION__ " EapHostPeerGetResponseAttributes failed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void eap::method_eapmsg::set_response_attributes(
|
||||||
|
_In_ const EapAttributes *pAttribs,
|
||||||
|
_Out_ EapPeerMethodOutput *pEapOutput)
|
||||||
|
{
|
||||||
|
// Set response attributes for EapHost peer.
|
||||||
|
EapHostPeerResponseAction action;
|
||||||
|
eap_error_runtime error;
|
||||||
|
DWORD dwResult = EapHostPeerSetResponseAttributes(
|
||||||
|
m_session_id,
|
||||||
|
pAttribs,
|
||||||
|
&action,
|
||||||
|
&error._Myptr);
|
||||||
|
if (dwResult == ERROR_SUCCESS) {
|
||||||
|
// Response attributes successfuly set.
|
||||||
|
action_to_output(action, pEapOutput);
|
||||||
|
} else if (error)
|
||||||
|
throw eap_runtime_error(*error , __FUNCTION__ " EapHostPeerGetResponseAttributes failed.");
|
||||||
|
else
|
||||||
|
throw win_runtime_error(dwResult, __FUNCTION__ " EapHostPeerGetResponseAttributes failed.");
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user