/* * ndis_events - Receive NdisMIndicateStatus() events using WMI * Copyright (c) 2004-2006, Jouni Malinen * * This software may be distributed under the terms of the BSD license. * See README for more details. */ #define _WIN32_WINNT 0x0400 #include "includes.h" #ifndef COBJMACROS #define COBJMACROS #endif /* COBJMACROS */ #include #include "common.h" static int wmi_refcnt = 0; static int wmi_first = 1; struct ndis_events_data { IWbemObjectSink sink; IWbemObjectSinkVtbl sink_vtbl; IWbemServices *pSvc; IWbemLocator *pLoc; HANDLE read_pipe, write_pipe, event_avail; UINT ref; int terminating; char *ifname; /* {GUID..} */ WCHAR *adapter_desc; }; #define BstrAlloc(x) (x) ? SysAllocString(x) : NULL #define BstrFree(x) if (x) SysFreeString(x) /* WBEM / WMI wrapper functions, to perform in-place conversion of WCHARs to * BSTRs */ HRESULT STDMETHODCALLTYPE call_IWbemServices_ExecQuery( IWbemServices *pSvc, LPCWSTR strQueryLanguage, LPCWSTR strQuery, long lFlags, IWbemContext *pCtx, IEnumWbemClassObject **ppEnum) { BSTR bsQueryLanguage, bsQuery; HRESULT hr; bsQueryLanguage = BstrAlloc(strQueryLanguage); bsQuery = BstrAlloc(strQuery); hr = IWbemServices_ExecQuery(pSvc, bsQueryLanguage, bsQuery, lFlags, pCtx, ppEnum); BstrFree(bsQueryLanguage); BstrFree(bsQuery); return hr; } HRESULT STDMETHODCALLTYPE call_IWbemServices_ExecNotificationQueryAsync( IWbemServices *pSvc, LPCWSTR strQueryLanguage, LPCWSTR strQuery, long lFlags, IWbemContext *pCtx, IWbemObjectSink *pResponseHandler) { BSTR bsQueryLanguage, bsQuery; HRESULT hr; bsQueryLanguage = BstrAlloc(strQueryLanguage); bsQuery = BstrAlloc(strQuery); hr = IWbemServices_ExecNotificationQueryAsync(pSvc, bsQueryLanguage, bsQuery, lFlags, pCtx, pResponseHandler); BstrFree(bsQueryLanguage); BstrFree(bsQuery); return hr; } HRESULT STDMETHODCALLTYPE call_IWbemLocator_ConnectServer( IWbemLocator *pLoc, LPCWSTR strNetworkResource, LPCWSTR strUser, LPCWSTR strPassword, LPCWSTR strLocale, long lSecurityFlags, LPCWSTR strAuthority, IWbemContext *pCtx, IWbemServices **ppNamespace) { BSTR bsNetworkResource, bsUser, bsPassword, bsLocale, bsAuthority; HRESULT hr; bsNetworkResource = BstrAlloc(strNetworkResource); bsUser = BstrAlloc(strUser); bsPassword = BstrAlloc(strPassword); bsLocale = BstrAlloc(strLocale); bsAuthority = BstrAlloc(strAuthority); hr = IWbemLocator_ConnectServer(pLoc, bsNetworkResource, bsUser, bsPassword, bsLocale, lSecurityFlags, bsAuthority, pCtx, ppNamespace); BstrFree(bsNetworkResource); BstrFree(bsUser); BstrFree(bsPassword); BstrFree(bsLocale); BstrFree(bsAuthority); return hr; } enum event_types { EVENT_CONNECT, EVENT_DISCONNECT, EVENT_MEDIA_SPECIFIC, EVENT_ADAPTER_ARRIVAL, EVENT_ADAPTER_REMOVAL }; static int ndis_events_get_adapter(struct ndis_events_data *events, const char *ifname, const char *desc); static int ndis_events_constructor(struct ndis_events_data *events) { events->ref = 1; if (!CreatePipe(&events->read_pipe, &events->write_pipe, NULL, 512)) { wpa_printf(MSG_ERROR, "CreatePipe() failed: %d", (int) GetLastError()); return -1; } events->event_avail = CreateEvent(NULL, TRUE, FALSE, NULL); if (events->event_avail == NULL) { wpa_printf(MSG_ERROR, "CreateEvent() failed: %d", (int) GetLastError()); CloseHandle(events->read_pipe); CloseHandle(events->write_pipe); return -1; } return 0; } static void ndis_events_destructor(struct ndis_events_data *events) { CloseHandle(events->read_pipe); CloseHandle(events->write_pipe); CloseHandle(events->event_avail); IWbemServices_Release(events->pSvc); IWbemLocator_Release(events->pLoc); if (--wmi_refcnt == 0) CoUninitialize(); } static HRESULT STDMETHODCALLTYPE ndis_events_query_interface(IWbemObjectSink *this, REFIID riid, void **obj) { *obj = NULL; if (IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_IWbemObjectSink)) { *obj = this; IWbemObjectSink_AddRef(this); return NOERROR; } return E_NOINTERFACE; } static ULONG STDMETHODCALLTYPE ndis_events_add_ref(IWbemObjectSink *this) { struct ndis_events_data *events = (struct ndis_events_data *) this; return ++events->ref; } static ULONG STDMETHODCALLTYPE ndis_events_release(IWbemObjectSink *this) { struct ndis_events_data *events = (struct ndis_events_data *) this; if (--events->ref != 0) return events->ref; ndis_events_destructor(events); wpa_printf(MSG_DEBUG, "ndis_events: terminated"); os_free(events->adapter_desc); os_free(events->ifname); os_free(events); return 0; } static int ndis_events_send_event(struct ndis_events_data *events, enum event_types type, char *data, size_t data_len) { char buf[512], *pos, *end; int _type; DWORD written; end = buf + sizeof(buf); _type = (int) type; os_memcpy(buf, &_type, sizeof(_type)); pos = buf + sizeof(_type); if (data) { if (2 + data_len > (size_t) (end - pos)) { wpa_printf(MSG_DEBUG, "Not enough room for send_event " "data (%d)", data_len); return -1; } *pos++ = data_len >> 8; *pos++ = data_len & 0xff; os_memcpy(pos, data, data_len); pos += data_len; } if (WriteFile(events->write_pipe, buf, pos - buf, &written, NULL)) { SetEvent(events->event_avail); return 0; } wpa_printf(MSG_INFO, "WriteFile() failed: %d", (int) GetLastError()); return -1; } static void ndis_events_media_connect(struct ndis_events_data *events) { wpa_printf(MSG_DEBUG, "MSNdis_StatusMediaConnect"); ndis_events_send_event(events, EVENT_CONNECT, NULL, 0); } static void ndis_events_media_disconnect(struct ndis_events_data *events) { wpa_printf(MSG_DEBUG, "MSNdis_StatusMediaDisconnect"); ndis_events_send_event(events, EVENT_DISCONNECT, NULL, 0); } static void ndis_events_media_specific(struct ndis_events_data *events, IWbemClassObject *pObj) { VARIANT vt; HRESULT hr; LONG lower, upper, k; UCHAR ch; char *data, *pos; size_t data_len; wpa_printf(MSG_DEBUG, "MSNdis_StatusMediaSpecificIndication"); /* This is the StatusBuffer from NdisMIndicateStatus() call */ hr = IWbemClassObject_Get(pObj, L"NdisStatusMediaSpecificIndication", 0, &vt, NULL, NULL); if (FAILED(hr)) { wpa_printf(MSG_DEBUG, "Could not get " "NdisStatusMediaSpecificIndication from " "the object?!"); return; } SafeArrayGetLBound(V_ARRAY(&vt), 1, &lower); SafeArrayGetUBound(V_ARRAY(&vt), 1, &upper); data_len = upper - lower + 1; data = os_malloc(data_len); if (data == NULL) { wpa_printf(MSG_DEBUG, "Failed to allocate buffer for event " "data"); VariantClear(&vt); return; } pos = data; for (k = lower; k <= upper; k++) { SafeArrayGetElement(V_ARRAY(&vt), &k, &ch); *pos++ = ch; } wpa_hexdump(MSG_DEBUG, "MediaSpecificEvent", (u8 *) data, data_len); VariantClear(&vt); ndis_events_send_event(events, EVENT_MEDIA_SPECIFIC, data, data_len); os_free(data); } static void ndis_events_adapter_arrival(struct ndis_events_data *events) { wpa_printf(MSG_DEBUG, "MSNdis_NotifyAdapterArrival"); ndis_events_send_event(events, EVENT_ADAPTER_ARRIVAL, NULL, 0); } static void ndis_events_adapter_removal(struct ndis_events_data *events) { wpa_printf(MSG_DEBUG, "MSNdis_NotifyAdapterRemoval"); ndis_events_send_event(events, EVENT_ADAPTER_REMOVAL, NULL, 0); } static HRESULT STDMETHODCALLTYPE ndis_events_indicate(IWbemObjectSink *this, long lObjectCount, IWbemClassObject __RPC_FAR *__RPC_FAR *ppObjArray) { struct ndis_events_data *events = (struct ndis_events_data *) this; long i; if (events->terminating) { wpa_printf(MSG_DEBUG, "ndis_events_indicate: Ignore " "indication - terminating"); return WBEM_NO_ERROR; } /* wpa_printf(MSG_DEBUG, "Notification received - %d object(s)", lObjectCount); */ for (i = 0; i < lObjectCount; i++) { IWbemClassObject *pObj = ppObjArray[i]; HRESULT hr; VARIANT vtClass, vt; hr = IWbemClassObject_Get(pObj, L"__CLASS", 0, &vtClass, NULL, NULL); if (FAILED(hr)) { wpa_printf(MSG_DEBUG, "Failed to get __CLASS from " "event."); break; } /* wpa_printf(MSG_DEBUG, "CLASS: '%S'", vtClass.bstrVal); */ hr = IWbemClassObject_Get(pObj, L"InstanceName", 0, &vt, NULL, NULL); if (FAILED(hr)) { wpa_printf(MSG_DEBUG, "Failed to get InstanceName " "from event."); VariantClear(&vtClass); break; } if (wcscmp(vtClass.bstrVal, L"MSNdis_NotifyAdapterArrival") == 0) { wpa_printf(MSG_DEBUG, "ndis_events_indicate: Try to " "update adapter description since it may " "have changed with new adapter instance"); ndis_events_get_adapter(events, events->ifname, NULL); } if (wcscmp(events->adapter_desc, vt.bstrVal) != 0) { wpa_printf(MSG_DEBUG, "ndis_events_indicate: Ignore " "indication for foreign adapter: " "InstanceName: '%S' __CLASS: '%S'", vt.bstrVal, vtClass.bstrVal); VariantClear(&vtClass); VariantClear(&vt); continue; } VariantClear(&vt); if (wcscmp(vtClass.bstrVal, L"MSNdis_StatusMediaSpecificIndication") == 0) { ndis_events_media_specific(events, pObj); } else if (wcscmp(vtClass.bstrVal, L"MSNdis_StatusMediaConnect") == 0) { ndis_events_media_connect(events); } else if (wcscmp(vtClass.bstrVal, L"MSNdis_StatusMediaDisconnect") == 0) { ndis_events_media_disconnect(events); } else if (wcscmp(vtClass.bstrVal, L"MSNdis_NotifyAdapterArrival") == 0) { ndis_events_adapter_arrival(events); } else if (wcscmp(vtClass.bstrVal, L"MSNdis_NotifyAdapterRemoval") == 0) { ndis_events_adapter_removal(events); } else { wpa_printf(MSG_DEBUG, "Unexpected event - __CLASS: '%S'", vtClass.bstrVal); } VariantClear(&vtClass); } return WBEM_NO_ERROR; } static HRESULT STDMETHODCALLTYPE ndis_events_set_status(IWbemObjectSink *this, long lFlags, HRESULT hResult, BSTR strParam, IWbemClassObject __RPC_FAR *pObjParam) { return WBEM_NO_ERROR; } static int notification_query(IWbemObjectSink *pDestSink, IWbemServices *pSvc, const char *class_name) { HRESULT hr; WCHAR query[256]; _snwprintf(query, 256, L"SELECT * FROM %S", class_name); wpa_printf(MSG_DEBUG, "ndis_events: WMI: %S", query); hr = call_IWbemServices_ExecNotificationQueryAsync( pSvc, L"WQL", query, 0, 0, pDestSink); if (FAILED(hr)) { wpa_printf(MSG_DEBUG, "ExecNotificationQueryAsync for %s " "failed with hresult of 0x%x", class_name, (int) hr); return -1; } return 0; } static int register_async_notification(IWbemObjectSink *pDestSink, IWbemServices *pSvc) { int i; const char *class_list[] = { "MSNdis_StatusMediaConnect", "MSNdis_StatusMediaDisconnect", "MSNdis_StatusMediaSpecificIndication", "MSNdis_NotifyAdapterArrival", "MSNdis_NotifyAdapterRemoval", NULL }; for (i = 0; class_list[i]; i++) { if (notification_query(pDestSink, pSvc, class_list[i]) < 0) return -1; } return 0; } void ndis_events_deinit(struct ndis_events_data *events) { events->terminating = 1; IWbemServices_CancelAsyncCall(events->pSvc, &events->sink); IWbemObjectSink_Release(&events->sink); /* * Rest of deinitialization is done in ndis_events_destructor() once * all reference count drops to zero. */ } static int ndis_events_use_desc(struct ndis_events_data *events, const char *desc) { char *tmp, *pos; size_t len; if (desc == NULL) { if (events->adapter_desc == NULL) return -1; /* Continue using old description */ return 0; } tmp = os_strdup(desc); if (tmp == NULL) return -1; pos = os_strstr(tmp, " (Microsoft's Packet Scheduler)"); if (pos) *pos = '\0'; len = os_strlen(tmp); events->adapter_desc = os_malloc((len + 1) * sizeof(WCHAR)); if (events->adapter_desc == NULL) { os_free(tmp); return -1; } _snwprintf(events->adapter_desc, len + 1, L"%S", tmp); os_free(tmp); return 0; } static int ndis_events_get_adapter(struct ndis_events_data *events, const char *ifname, const char *desc) { HRESULT hr; IWbemServices *pSvc; #define MAX_QUERY_LEN 256 WCHAR query[MAX_QUERY_LEN]; IEnumWbemClassObject *pEnumerator; IWbemClassObject *pObj; ULONG uReturned; VARIANT vt; int len, pos; /* * Try to get adapter descriptor through WMI CIMv2 Win32_NetworkAdapter * to have better probability of matching with InstanceName from * MSNdis events. If this fails, use the provided description. */ os_free(events->adapter_desc); events->adapter_desc = NULL; hr = call_IWbemLocator_ConnectServer( events->pLoc, L"ROOT\\CIMV2", NULL, NULL, 0, 0, 0, 0, &pSvc); if (FAILED(hr)) { wpa_printf(MSG_ERROR, "ndis_events: Could not connect to WMI " "server (ROOT\\CIMV2) - error 0x%x", (int) hr); return ndis_events_use_desc(events, desc); } wpa_printf(MSG_DEBUG, "ndis_events: Connected to ROOT\\CIMV2."); _snwprintf(query, MAX_QUERY_LEN, L"SELECT Index FROM Win32_NetworkAdapterConfiguration " L"WHERE SettingID='%S'", ifname); wpa_printf(MSG_DEBUG, "ndis_events: WMI: %S", query); hr = call_IWbemServices_ExecQuery( pSvc, L"WQL", query, WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &pEnumerator); if (!SUCCEEDED(hr)) { wpa_printf(MSG_DEBUG, "ndis_events: Failed to query interface " "GUID from Win32_NetworkAdapterConfiguration: " "0x%x", (int) hr); IWbemServices_Release(pSvc); return ndis_events_use_desc(events, desc); } uReturned = 0; hr = IEnumWbemClassObject_Next(pEnumerator, WBEM_INFINITE, 1, &pObj, &uReturned); if (!SUCCEEDED(hr) || uReturned == 0) { wpa_printf(MSG_DEBUG, "ndis_events: Failed to find interface " "GUID from Win32_NetworkAdapterConfiguration: " "0x%x", (int) hr); IEnumWbemClassObject_Release(pEnumerator); IWbemServices_Release(pSvc); return ndis_events_use_desc(events, desc); } IEnumWbemClassObject_Release(pEnumerator); VariantInit(&vt); hr = IWbemClassObject_Get(pObj, L"Index", 0, &vt, NULL, NULL); if (!SUCCEEDED(hr)) { wpa_printf(MSG_DEBUG, "ndis_events: Failed to get Index from " "Win32_NetworkAdapterConfiguration: 0x%x", (int) hr); IWbemServices_Release(pSvc); return ndis_events_use_desc(events, desc); } _snwprintf(query, MAX_QUERY_LEN, L"SELECT Name,PNPDeviceID FROM Win32_NetworkAdapter WHERE " L"Index=%d", vt.uintVal); wpa_printf(MSG_DEBUG, "ndis_events: WMI: %S", query); VariantClear(&vt); IWbemClassObject_Release(pObj); hr = call_IWbemServices_ExecQuery( pSvc, L"WQL", query, WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &pEnumerator); if (!SUCCEEDED(hr)) { wpa_printf(MSG_DEBUG, "ndis_events: Failed to query interface " "from Win32_NetworkAdapter: 0x%x", (int) hr); IWbemServices_Release(pSvc); return ndis_events_use_desc(events, desc); } uReturned = 0; hr = IEnumWbemClassObject_Next(pEnumerator, WBEM_INFINITE, 1, &pObj, &uReturned); if (!SUCCEEDED(hr) || uReturned == 0) { wpa_printf(MSG_DEBUG, "ndis_events: Failed to find interface " "from Win32_NetworkAdapter: 0x%x", (int) hr); IEnumWbemClassObject_Release(pEnumerator); IWbemServices_Release(pSvc); return ndis_events_use_desc(events, desc); } IEnumWbemClassObject_Release(pEnumerator); hr = IWbemClassObject_Get(pObj, L"Name", 0, &vt, NULL, NULL); if (!SUCCEEDED(hr)) { wpa_printf(MSG_DEBUG, "ndis_events: Failed to get Name from " "Win32_NetworkAdapter: 0x%x", (int) hr); IWbemClassObject_Release(pObj); IWbemServices_Release(pSvc); return ndis_events_use_desc(events, desc); } wpa_printf(MSG_DEBUG, "ndis_events: Win32_NetworkAdapter::Name='%S'", vt.bstrVal); events->adapter_desc = _wcsdup(vt.bstrVal); VariantClear(&vt); /* * Try to get even better candidate for matching with InstanceName * from Win32_PnPEntity. This is needed at least for some USB cards * that can change the InstanceName whenever being unplugged and * plugged again. */ hr = IWbemClassObject_Get(pObj, L"PNPDeviceID", 0, &vt, NULL, NULL); if (!SUCCEEDED(hr)) { wpa_printf(MSG_DEBUG, "ndis_events: Failed to get PNPDeviceID " "from Win32_NetworkAdapter: 0x%x", (int) hr); IWbemClassObject_Release(pObj); IWbemServices_Release(pSvc); if (events->adapter_desc == NULL) return ndis_events_use_desc(events, desc); return 0; /* use Win32_NetworkAdapter::Name */ } wpa_printf(MSG_DEBUG, "ndis_events: Win32_NetworkAdapter::PNPDeviceID=" "'%S'", vt.bstrVal); len = _snwprintf(query, MAX_QUERY_LEN, L"SELECT Name FROM Win32_PnPEntity WHERE DeviceID='"); if (len < 0 || len >= MAX_QUERY_LEN - 1) { VariantClear(&vt); IWbemClassObject_Release(pObj); IWbemServices_Release(pSvc); if (events->adapter_desc == NULL) return ndis_events_use_desc(events, desc); return 0; /* use Win32_NetworkAdapter::Name */ } /* Escape \ as \\ */ for (pos = 0; vt.bstrVal[pos] && len < MAX_QUERY_LEN - 2; pos++) { if (vt.bstrVal[pos] == '\\') { if (len >= MAX_QUERY_LEN - 3) break; query[len++] = '\\'; } query[len++] = vt.bstrVal[pos]; } query[len++] = L'\''; query[len] = L'\0'; VariantClear(&vt); IWbemClassObject_Release(pObj); wpa_printf(MSG_DEBUG, "ndis_events: WMI: %S", query); hr = call_IWbemServices_ExecQuery( pSvc, L"WQL", query, WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, NULL, &pEnumerator); if (!SUCCEEDED(hr)) { wpa_printf(MSG_DEBUG, "ndis_events: Failed to query interface " "Name from Win32_PnPEntity: 0x%x", (int) hr); IWbemServices_Release(pSvc); if (events->adapter_desc == NULL) return ndis_events_use_desc(events, desc); return 0; /* use Win32_NetworkAdapter::Name */ } uReturned = 0; hr = IEnumWbemClassObject_Next(pEnumerator, WBEM_INFINITE, 1, &pObj, &uReturned); if (!SUCCEEDED(hr) || uReturned == 0) { wpa_printf(MSG_DEBUG, "ndis_events: Failed to find interface " "from Win32_PnPEntity: 0x%x", (int) hr); IEnumWbemClassObject_Release(pEnumerator); IWbemServices_Release(pSvc); if (events->adapter_desc == NULL) return ndis_events_use_desc(events, desc); return 0; /* use Win32_NetworkAdapter::Name */ } IEnumWbemClassObject_Release(pEnumerator); hr = IWbemClassObject_Get(pObj, L"Name", 0, &vt, NULL, NULL); if (!SUCCEEDED(hr)) { wpa_printf(MSG_DEBUG, "ndis_events: Failed to get Name from " "Win32_PnPEntity: 0x%x", (int) hr); IWbemClassObject_Release(pObj); IWbemServices_Release(pSvc); if (events->adapter_desc == NULL) return ndis_events_use_desc(events, desc); return 0; /* use Win32_NetworkAdapter::Name */ } wpa_printf(MSG_DEBUG, "ndis_events: Win32_PnPEntity::Name='%S'", vt.bstrVal); os_free(events->adapter_desc); events->adapter_desc = _wcsdup(vt.bstrVal); VariantClear(&vt); IWbemClassObject_Release(pObj); IWbemServices_Release(pSvc); if (events->adapter_desc == NULL) return ndis_events_use_desc(events, desc); return 0; } struct ndis_events_data * ndis_events_init(HANDLE *read_pipe, HANDLE *event_avail, const char *ifname, const char *desc) { HRESULT hr; IWbemObjectSink *pSink; struct ndis_events_data *events; events = os_zalloc(sizeof(*events)); if (events == NULL) { wpa_printf(MSG_ERROR, "Could not allocate sink for events."); return NULL; } events->ifname = os_strdup(ifname); if (events->ifname == NULL) { os_free(events); return NULL; } if (wmi_refcnt++ == 0) { hr = CoInitializeEx(0, COINIT_MULTITHREADED); if (FAILED(hr)) { wpa_printf(MSG_ERROR, "CoInitializeEx() failed - " "returned 0x%x", (int) hr); os_free(events); return NULL; } } if (wmi_first) { /* CoInitializeSecurity() must be called once and only once * per process, so let's use wmi_first flag to protect against * multiple calls. */ wmi_first = 0; hr = CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_PKT_PRIVACY, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_SECURE_REFS, NULL); if (FAILED(hr)) { wpa_printf(MSG_ERROR, "CoInitializeSecurity() failed " "- returned 0x%x", (int) hr); os_free(events); return NULL; } } hr = CoCreateInstance(&CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, &IID_IWbemLocator, (LPVOID *) (void *) &events->pLoc); if (FAILED(hr)) { wpa_printf(MSG_ERROR, "CoCreateInstance() failed - returned " "0x%x", (int) hr); CoUninitialize(); os_free(events); return NULL; } if (ndis_events_get_adapter(events, ifname, desc) < 0) { CoUninitialize(); os_free(events); return NULL; } wpa_printf(MSG_DEBUG, "ndis_events: use adapter descriptor '%S'", events->adapter_desc); hr = call_IWbemLocator_ConnectServer( events->pLoc, L"ROOT\\WMI", NULL, NULL, 0, 0, 0, 0, &events->pSvc); if (FAILED(hr)) { wpa_printf(MSG_ERROR, "Could not connect to server - error " "0x%x", (int) hr); CoUninitialize(); os_free(events->adapter_desc); os_free(events); return NULL; } wpa_printf(MSG_DEBUG, "Connected to ROOT\\WMI."); ndis_events_constructor(events); pSink = &events->sink; pSink->lpVtbl = &events->sink_vtbl; events->sink_vtbl.QueryInterface = ndis_events_query_interface; events->sink_vtbl.AddRef = ndis_events_add_ref; events->sink_vtbl.Release = ndis_events_release; events->sink_vtbl.Indicate = ndis_events_indicate; events->sink_vtbl.SetStatus = ndis_events_set_status; if (register_async_notification(pSink, events->pSvc) < 0) { wpa_printf(MSG_DEBUG, "Failed to register async " "notifications"); ndis_events_destructor(events); os_free(events->adapter_desc); os_free(events); return NULL; } *read_pipe = events->read_pipe; *event_avail = events->event_avail; return events; }