/*
 * ndis_events - Receive NdisMIndicateStatus() events using WMI
 * Copyright (c) 2004-2006, Jouni Malinen <j@w1.fi>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * Alternatively, this software may be distributed under the terms of BSD
 * license.
 *
 * See README and COPYING for more details.
 */

#define _WIN32_WINNT    0x0400

#include "includes.h"

#ifndef COBJMACROS
#define COBJMACROS
#endif /* COBJMACROS */
#include <wbemidl.h>

#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, "Unepected 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;
}
