2025-11-28 00:35:46 +09:00

616 lines
20 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 <napprotocol.h>
#include <NapUtil.h>
#include <stdio.h>
#include <new>
#include "SDKCommon.h"
#include "Callback.h"
using namespace SDK_SAMPLE_COMMON;
using namespace SDK_SAMPLE_SHA;
SDK_SHA_FIXUPSTATE g_SdkShaHealthState = NOFIXESNEEDED;
BOOL g_setHealthySoh = true;
// Create instance
INapSystemHealthAgentCallback* ShaCallback::CreateInstance(
_In_ INapSystemHealthAgentBinding* pBinding)
{
ShaCallback* pTemp = new (std::nothrow) ShaCallback();
if (!pTemp)
{
wprintf(L"\nShaCallback::CreateInstance: Failed to create Callback instance\n");
goto Cleanup;
}
pTemp->AddRef();
pTemp->m_pBinding = pBinding;
Cleanup:
return pTemp;
}
// Constructor
ShaCallback::ShaCallback() :
m_pBinding(NULL),
m_cRef(0)
{
}
// Destructor
ShaCallback::~ShaCallback()
{
}
//
// SDK Note:
// This callback is used by the NAPAgent to request a Statement of Health (SoH) from
// the System Health Agent (SHA). An SHA must implement this interface, and within it,
// it must construct its own SoH 'blob' and pass it back to the NAPAgent
// This data will then be communicated to the NAP Server for evaluation.
// See MSDN for latest documentation on this function.
//
STDMETHODIMP ShaCallback::GetSoHRequest(
__RPC__in_opt INapSystemHealthAgentRequest* pRequest)
{
HRESULT hr = S_OK;
wprintf(L"\nShaCallback::GetSoHRequest(): called by NAPAgent\n");
INapSoHConstructor *pISohConstructor = NULL;
SoHRequest *pSohRequest = NULL;
// sanity check - ensure that the passed request pointer is not NULL
if (!pRequest)
{
hr = E_POINTER;
wprintf(L"\nShaCallback::GetSoHRequest : received NULL Request pointer\n");
goto Cleanup;
}
//
// Create an SoH constructor interface.
// Passing in our SystemHealth ID, and the type of SoH we want to create (request vs response)
//
hr = CreateOutputSoHConstructor(pISohConstructor, QuarSampleSystemHealthId, SOH_REQUEST);
if (FAILED(hr))
{
wprintf(L"\nShaCallback::GetSoHRequest: CreateOutputSoHConstructor() failed (error = 0x%08x)\n", hr);
pISohConstructor = NULL;
goto Cleanup;
}
//
// Populate the data into the SoH Constructor
//
hr = FillSoHRequest(pISohConstructor);
if (FAILED(hr))
{
wprintf(L"\nShaCallback::GetSoHRequest(): FillSoHRequest() failed (error = 0x%08x)\n", hr);
goto Cleanup;
}
//
// Get the constructed SoH data from the SoH Constructor
//
hr = pISohConstructor->GetSoH(&pSohRequest);
if (FAILED(hr))
{
wprintf(L"\nShaCallback::GetSoHRequest(): SoHConstructor->GetSoH failed (error = 0x%08x)\n", hr);
pSohRequest = NULL;
goto Cleanup;
}
//
// Ensure that NULL was not passed back
//
if (! pSohRequest)
{
hr = E_POINTER;
wprintf(L"\nShaCallback::GetSoHRequest(): Final Request SoH data pointer was NULL!\n");
goto Cleanup;
}
//
// Pass the SoH data out via the Request object that was passed in
//
hr = pRequest->SetSoHRequest(pSohRequest, TRUE);
if (FAILED(hr))
{
wprintf(L"\nShaCallback::GetSoHRequest(): pShaRequest->SetSoHRequest failed (error = 0x%08x)\n", hr);
}
Cleanup:
ReleaseObject(pISohConstructor);
::FreeSoH(pSohRequest);
return hr;
}
//
// SDK Note:
// This callback is used by the NAP Agent to pass a received SoH Response to the
// SHA for processing. It is called whenever the NAP Agent has an SoH response that
// is destined for this SHA.
// See MSDN for latest documentation on this function.
STDMETHODIMP ShaCallback::ProcessSoHResponse(
__RPC__in_opt INapSystemHealthAgentRequest* pResponse)
{
wprintf(L"\nShaCallback::ProcessSoHResponse() called by NAPAgent\n");
HRESULT hr = S_OK;
SoHResponse* pSohResponse = NULL;
INapSoHProcessor *pSohProcessor = NULL;
SystemHealthEntityId systemHealthId = 0;
UINT8 flags = 0;
BOOL doFixup = FALSE;
// sanity check - ensure that the passed response pointer is not NULL
if (!pResponse)
{
hr = E_POINTER;
wprintf(L"\nShaCallback::ProcessSoHResponse NULL INapSystemHealthAgentRequest pointer\n");
goto Cleanup;
}
//
// Get the response SoH from NAPAgent, as well as the flags (see below)
//
hr = pResponse->GetSoHResponse(&pSohResponse, &flags);
if (FAILED(hr))
{
// Couldn't get the response Log an error and return
wprintf(L"\nShaCallback::ProcessSoHResponse():pResponse->GetSoHResponse failed (error = 0x%08x)\n", hr);
pSohResponse = NULL;
goto Cleanup;
}
// Determine whether to attempt auto-remediation if fixes are needed
if ((flags & shaFixup) == shaFixup)
{
//Instructed to do fixup
doFixup = TRUE;
}
else
{
//not instructed to do fixup
doFixup = FALSE;
}
//
// Wrap it inside an INapSoHProcessor object.
// This will handle parsing of the SoH data for us, making it easier to retrieve the
// desired data from the SoH.
//
hr = CreateInputSoHProcessor(pSohProcessor, systemHealthId, SOH_RESPONSE, pSohResponse);
if (FAILED(hr))
{
wprintf(L"\nShaCallback::ProcessSoHResponse: CreateInputSoHProcessor(): Couldn't create an SoH Parser object! (error %#x)\n", hr);
pSohProcessor = NULL;
goto Cleanup;
}
//
// SDK Note:
// In this sample, we set some flags to notify what action this SHA needs to take, then
// other callbacks use these flags to generate notification output to the console.
// In a real SHA, one would want to somehow notify a worker thread that updates are
// needed so that that worker can perform the updates
//
hr = HandleSoHResponse(pSohProcessor, doFixup);
if (FAILED(hr))
{
wprintf(L" ShaCallback::ProcessSoHResponse():HandleSoHResponse failed (error = 0x%08x)\n", hr);
goto Cleanup;
}
Cleanup:
ReleaseObject(pSohProcessor);
FreeSoH(pSohResponse);
return hr;
}
//
// SDK Note:
// This callback is used by the NAPAgent to notify SHAs that the system's NAP
// isolation state has changed. If an SHA has activities to be done when state
// changes, it could be triggered here
// See MSDN for latest documentation on this function.
//
STDMETHODIMP ShaCallback::NotifySystemIsolationStateChange()
{
wprintf(L"ShaCallback::NotifySystemIsolationStateChange() called by NAPAgent\n");
HRESULT hr = S_OK;
IsolationInfo* pStatus = NULL;
BOOL unknownConnections = FALSE;
// ensure that the Binding object is set before attempting to use it
if (!m_pBinding)
{
wprintf(L" ShaCallback::NotifySystemIsolationStateChange():m_pBinding is NULL");
goto Cleanup;
}
//
// SDK Note:
// The health agent can query the system quarantine state
// using the Binding::GetSystemIsolationInfo()
//
hr = m_pBinding->GetSystemIsolationInfo(&pStatus, &unknownConnections);
if (FAILED(hr))
{
wprintf(L" ShaCallback::NotifySystemIsolationStateChange():m_pBinding->GetSystemIsolationInfo() failed (error = 0x%08x)\n",
hr);
pStatus = NULL;
goto Cleanup;
}
//
// for this example, we'll just output the current state to the console
//
wprintf(L"System quar state = %d, prob time = %x %x, url = %s, any_connections_in_unknown_state? = %d\n",
pStatus->isolationState,
pStatus->probEndTime.dwHighDateTime, pStatus->probEndTime.dwLowDateTime,
(pStatus->failureUrl.string == 0) ? 0 : pStatus->failureUrl.string,
unknownConnections);
Cleanup:
FreeIsolationInfo(pStatus);
return hr;
}
//
// SDK Note:
// This callback is used by the NAPAgent to query the SHA for its current fixup status
// while that SHA is processing an SoH Response
// See MSDN for latest documentation on this function.
//
STDMETHODIMP ShaCallback::GetFixupInfo(
__RPC__deref_out_opt FixupInfo** ppStatus)
{
wprintf(L"\nShaCallback::GetFixupInfo() called by NAPAgent\n");
HRESULT hr = S_OK;
if (ppStatus == NULL)
{
hr = E_POINTER;
wprintf(L"\nShaCallback::GetFixupInfo() bad pointer for ppStatus passed in\n");
goto Cleanup;
}
// The caller should free this memory using CoTaskMemFree
hr = AllocFixupInfo(ppStatus, NUMBER_OF_HRESULTS);
if (FAILED(hr))
{
goto Cleanup;
}
// Fill ppStatus
// SDK Note:
// This should be filled according to the current Fix up status
// This is a simple example and only returns 1 result code
switch (g_SdkShaHealthState)
{
case NOFIXESNEEDED:
// In this example, this case will be hit when the client is healthy
wprintf(L"ShaCallback::GetFixupInfo: Returning HealthState of NOFIXESNEEDED");
(*ppStatus)->fixupMsgId = SDK_SAMPLE_GENERIC_FIXUP_SUCCESS_MSG_ID;
(*ppStatus)->percentage = 100;
(*ppStatus)->state = fixupStateSuccess;
break;
case FIXESINPROGRESS:
// In this example, this case will be hit when unhealthy, but only when the
//'auto-remediate' flag is passed back from the NAP Server
//
// SDK Note:
// The 'auto-remediate' flag indicates that the SHA should
// automatically attempt to fix the client's health state, if possible.
//
wprintf(L"ShaCallback::GetFixupInfo: Returning HealthState of FIXESINPROGRESS");
(*ppStatus)->fixupMsgId = SDK_SAMPLE_GENERIC_FIXUP_INPROGRESS_MSG_ID;
(*ppStatus)->percentage = 50;
(*ppStatus)->state = fixupStateInProgress;
break;
case FIXESNEEDED:
default:
// In this example, this case will be hit when unhealthy and 'auto-remediate'
// is NOT indicated by NAP Server
//
// SDK Note:
// The 'auto-remediate' flag indicates that the SHA should
// automatically attempt to fix the client's health state, if possible. If it is NOT
// set, then the SHA should not update the client, but should leave it in its
// current state.
//
wprintf(L"ShaCallback::GetFixupInfo: Returning HealthState of FIXESNEEDED");
(*ppStatus)->fixupMsgId = SDK_SAMPLE_CLIENT_NOT_PATCHED_MSG_ID;
(*ppStatus)->percentage = percentageNotSupported;
(*ppStatus)->state = fixupStateCouldNotUpdate;
break;
}
(*ppStatus)->resultCodes.count = NUMBER_OF_HRESULTS; // 1 HRESULT
*((*ppStatus)->resultCodes.results) = S_OK;
Cleanup:
return hr;
}
// Fill the SoH with the parameters
HRESULT ShaCallback::FillSoHRequest(
_In_ INapSoHConstructor* pISohRequest)
{
HRESULT hr = S_OK;
SoHAttributeValue value = {0};
//
// Set up the SDK SHA's Vendor Specific data attribute
//
value.vendorSpecificVal.vendorId = QuarSampleSystemHealthId;
value.vendorSpecificVal.size = SDK_CLIENT_VENDOR_DATA_SIZE;
//
// SDK Note:
// Append any optional attributes appropriate for the given
// business logic. (I.e., Health-Class, Software-Version,
// Time-of-Last-Update, etc.)
// The data sent will be specific to the Health Validator/Agent pair in question
// if sending healthy SoH, set healthy data, otherwise set unhealthy data
if (g_setHealthySoh)
{
wprintf(L"\nShaCallback::FillSoHRequest(): Setting outbound SoH data: Healthy\n");
value.vendorSpecificVal.vendorSpecificData = (PBYTE)&SDK_CLIENT_VENDOR_DATA_HEALTHY;
}
else
{
wprintf(L"\nShaCallback::FillSoHRequest(): Setting outbound SoH data: Unhealthy\n");
value.vendorSpecificVal.vendorSpecificData = (PBYTE)&SDK_CLIENT_VENDOR_DATA_UNHEALTHY;
}
//
// add the data to the passed in SoH Constructor
//
hr = pISohRequest->AppendAttribute(sohAttributeTypeVendorSpecific,&value);
if (FAILED(hr))
{
wprintf(L"\nShaCallback::FillSoHRequest(): pISohRequest->AppendVendorSpecificAttribute(sohAttributeTypeVendorSpecific) (error = 0x%08x)\n", hr);
}
return hr;
}
// Called from within this implementation of ProcessSoHResponse, this function wraps up
// the actual evaluation of the SoH data to determine the health state as indicated by
// the NAP Server's response
HRESULT ShaCallback::HandleSoHResponse(
_In_ INapSoHProcessor * pSohProcessor,
_In_ BOOL doFixup)
{
HRESULT hr = S_OK;
UINT16 index = 0;
SoHAttributeType attrType = (SoHAttributeType) 0;
SoHAttributeValue *pAttrValue = NULL;
//
// SDK Note:
// Each vendor should implement this section based upon their business logic and
// on the state indicated by the SoH Response data
//
// sanity check - Ensure that an SoH Processor object was passed in
if (! pSohProcessor)
{
// don't change our state if we can't read the SoH
hr = NAP_E_MISSING_SOH;
wprintf(L"\nShaCallback::HandleSoHResponse missing SoH Processor");
goto Cleanup;
}
hr = pSohProcessor->FindNextAttribute(0,
sohAttributeTypeComplianceResultCodes,
&index);
if (FAILED(hr))
{
// don't change our state if we can't read the SoH
wprintf(L"\nShaCallback::HandleSoHResponse(): Failed to get sohAttributeTypeComplianceResultCodes (error = 0x%08x)\n", hr);
goto Cleanup;
}
hr = pSohProcessor->GetAttribute(index, &attrType, &pAttrValue);
if (FAILED(hr))
{
// don't change our state if we can't read the SoH
wprintf(L"\nShaCallback::HandleSoHResponse(): GetAttribute failed (error = 0x%08x)\n", hr);
attrType = (SoHAttributeType) 0;
pAttrValue = NULL;
goto Cleanup;
}
// for this sample, only reading 1 attribute
// if more or less attributes are returned from the NAP Server via NAP Agent, then
// this sample SHA will fail out.
if ( NUMBER_OF_HRESULTS != pAttrValue->codesVal.count )
{
hr = NAP_E_INVALID_PACKET;
wprintf(L"\nShaCallback::HandleSoHResponse(): Incorrect number of results returned from NAP Server\n");
goto Cleanup;
}
// SDK Note:
// in this example will handle the first result
// each vendor can have as many results as needed
// The logic will vary from vendor to another, so according to the result they should act
// for example if the result code = QUAR_E_NOTPATCHED and auto-remediate is set,
// SHA may attempt to install patches, etc
HRESULT resultCode = pAttrValue->codesVal.results[0];
if (FAILED(resultCode))
{
//
// Update a flag that fixes are required
// Fixes should be handled by a separate thread, so as not to block
// the NAP system.
// The NAPAgent will query fixup state via the GetFixupInfo callback.
//
wprintf(L"Client needs fixes (Result code = %x)\n", resultCode);
g_SdkShaHealthState = FIXESNEEDED;
if (TRUE == doFixup)
{
wprintf(L"Client needs fixes and instructed to auto-remediate (Result code = %x)\n", resultCode);
g_SdkShaHealthState = FIXESINPROGRESS;
}
}
else
{
wprintf(L"Compliant machine (Result code = %x)\n", resultCode);
g_SdkShaHealthState = NOFIXESNEEDED;
}
//
// SDK Note:
// This method must not hold references to the request object once
// this call has completed.
//
// After saving the SoHResponse, ProcessSoHResponse() should notify a
// worker thread, and return immediately
//
// The worker thread would be responsible for all fixup work
// Currently, this sample SHA is single-threaded, so it acts as a
// synchronous, blocking call, which returns to the QA once it has
// completed all necessary fixup work.
//
// When writing a multithreaded SHA, the behavior is somewhat different:
// When the NAP Agent calls into this SHA's ProcessSoHResponse(),
// the SHA will start a worker thread, then exit immediately.
// The worker thread will perform all fixup steps, and should
// indicate its percentage-complete status in a FixupStatus value. The
// NAP Agent will call <GetFixupInfo> to query this status, to determine
// what to display to the user in the NAP Status window.
//
Cleanup:
FreeSoHAttributeValue(attrType,pAttrValue);
return hr;
}
//
// SDK Note:
// This callback is used by the NAPAgent when determining whether a new NAP transaction
// is required due to a system state change.
// The SHA should compare the SoHs and return isEqual as TRUE if the SoHs are semantically
// equal.
// See MSDN for latest documentation on this function.
//
STDMETHODIMP ShaCallback::CompareSoHRequests(
/* in */ __RPC__in const SoHRequest* lhs,
/* in */ __RPC__in const SoHRequest* rhs,
/* out */ __RPC__out BOOL* isEqual)
{
HRESULT hr = S_OK;
wprintf(L"\nShaCallback::CompareSoHRequests() called by NAPAgent\n");
// SDK Note:
// If SHAs have put incremental IDs or time-stamps into their SoH, then 2 SoHs may
// be semantically equal (i.e. they may convey the same health information), but they may
// be byte-wise unequal. SHAs should be careful to implement this function such that they
// check for semantic equality on SoHs.
//
// If SHAs have not put any time-stamps or Ids into their SoH, they may choose to not
// implement this function and return E_NOTIMPL. In this case, the NapAgent performs
// a byte-wise comparison on the SoHRequests.
UNREFERENCED_PARAMETER(lhs);
UNREFERENCED_PARAMETER(rhs);
UNREFERENCED_PARAMETER(isEqual);
wprintf(L"ShaCallback::CompareSoHRequest: SHA does not support CompareSoHRequests API, so QA will compare bytes on its own\n");
hr = E_NOTIMPL;
return hr;
}
//
// This callback is used by the NAPAgent to notify the SHA that an outstanding SoHRequest
// did not receive a corresponding SoHResponse. The SHA may use this to clean up state
// However, this is a best-effort notification, and may not be called in all cases. Therefore,
// an SHA should not rely entirely upon this callback for cleaning up state.
// See MSDN for latest documentation on this function.
//
STDMETHODIMP ShaCallback::NotifyOrphanedSoHRequest(
/* in */ __RPC__in const CorrelationId* correlationId)
{
HRESULT hr = S_OK;
wprintf(L"NapAgent notified about OrphanedSohRequest (no response received for previously generated SoHRequest)\n");
UNREFERENCED_PARAMETER(correlationId);
return hr;
}
// Implementation of IUnknown
STDMETHODIMP ShaCallback::QueryInterface(
__RPC__in const IID& iid,
__RPC__out void** ppv)
{
if (iid == IID_IUnknown)
{
*ppv = static_cast<IUnknown*>(this);
}
else if (iid == IID_INapSystemHealthAgentCallback)
{
*ppv = static_cast<INapSystemHealthAgentCallback*>(this);
}
else
{
*ppv = NULL;
return E_NOINTERFACE;
}
reinterpret_cast<IUnknown*>(*ppv)->AddRef();
return S_OK;
}
STDMETHODIMP_(ULONG) ShaCallback::AddRef()
{
return InterlockedIncrement(&m_cRef);
}
STDMETHODIMP_(ULONG) ShaCallback::Release()
{
ULONG cRef = InterlockedDecrement(&m_cRef);
if (cRef == 0)
{
delete this;
}
return cRef;
}