From 7e3740018d314d62aae4dba3923a8883449492be Mon Sep 17 00:00:00 2001 From: Simon Rozman Date: Tue, 21 Jul 2020 16:38:00 +0200 Subject: api: finish porting from wireguard-go Signed-off-by: Simon Rozman --- api/api.h | 70 ++- api/api.vcxproj | 3 +- api/api.vcxproj.filters | 3 + api/devmgmt.c | 1082 ++++++++++++++++++++++++++++++++++------------- api/exports.def | 9 + api/registry.c | 411 ++++++++++++++++++ 6 files changed, 1293 insertions(+), 285 deletions(-) create mode 100644 api/registry.c diff --git a/api/api.h b/api/api.h index 0e31fd7..ef3f669 100644 --- a/api/api.h +++ b/api/api.h @@ -6,6 +6,7 @@ #pragma once #include +#include typedef _Return_type_success_(return == ERROR_SUCCESS) DWORD WINSTATUS; extern HINSTANCE ResourceModule; @@ -37,6 +38,42 @@ NciInit(); void NciCleanup(); +WINSTATUS +RegistryOpenKeyWait( + _In_ HKEY Key, + _In_z_count_c_(MAX_PATH) LPCWSTR Path, + _In_ DWORD Access, + _In_ DWORD Timeout, + _Out_ HKEY *KeyOut); + +WINSTATUS +RegistryWaitForKey(_In_ HKEY Key, _In_z_count_c_(MAX_PATH) LPCWSTR Path, _In_ DWORD Timeout); + +WINSTATUS +RegistryGetString(_Inout_ LPWSTR *Buf, _In_ DWORD Len, _In_ DWORD ValueType); + +WINSTATUS +RegistryGetMultiString(_Inout_ LPWSTR *Buf, _In_ DWORD Len, _In_ DWORD ValueType); + +WINSTATUS +RegistryQueryString(_In_ HKEY Key, _In_opt_z_ LPCWSTR Name, _Out_ LPWSTR *Value); + +WINSTATUS +RegistryQueryStringWait(_In_ HKEY Key, _In_opt_z_ LPCWSTR Name, _In_ DWORD Timeout, _Out_ LPWSTR *Value); + +WINSTATUS +RegistryQueryDWORD(_In_ HKEY Key, _In_opt_z_ LPCWSTR Name, _Out_ DWORD *Value); + +WINSTATUS +RegistryQueryDWORDWait(_In_ HKEY Key, _In_opt_z_ LPCWSTR Name, _In_ DWORD Timeout, _Out_ DWORD *Value); + +WINSTATUS WINAPI +WintunGetVersion( + _Out_ DWORD *DriverVersionMaj, + _Out_ DWORD *DriverVersionMin, + _Out_ DWORD *NdisVersionMaj, + _Out_ DWORD *NdisVersionMin); + #define MAX_POOL 256 #define MAX_INSTANCE_ID MAX_PATH /* TODO: Is MAX_PATH always enough? */ @@ -53,4 +90,35 @@ VOID WINAPI WintunFreeAdapter(_In_ WINTUN_ADAPTER *Adapter); WINSTATUS WINAPI -WintunGetAdapter(_In_z_count_c_(MAX_POOL) LPCWSTR Pool, _In_z_ LPCWSTR IfName, _Out_ WINTUN_ADAPTER **Adapter); +WintunGetAdapter(_In_z_count_c_(MAX_POOL) LPCWSTR Pool, _In_z_ LPCWSTR Name, _Out_ WINTUN_ADAPTER **Adapter); + +WINSTATUS WINAPI +WintunGetAdapterName(_In_ const WINTUN_ADAPTER *Adapter, _Out_cap_c_(MAX_ADAPTER_NAME) LPWSTR Name); + +WINSTATUS WINAPI +WintunSetAdapterName(_In_ const WINTUN_ADAPTER *Adapter, _In_z_count_c_(MAX_ADAPTER_NAME) LPCWSTR Name); + +void WINAPI +WintunGetAdapterGUID(_In_ const WINTUN_ADAPTER *Adapter, _Out_ GUID *Guid); + +void WINAPI +WintunGetAdapterLUID(_In_ const WINTUN_ADAPTER *Adapter, _Out_ LUID *Luid); + +WINSTATUS WINAPI +WintunGetAdapterDeviceObject(_In_ const WINTUN_ADAPTER *Adapter, _Out_ HANDLE *Handle); + +WINSTATUS WINAPI +WintunCreateAdapter( + _In_z_count_c_(MAX_POOL) LPCWSTR Pool, + _In_z_ LPCWSTR Name, + _In_opt_ const GUID *RequestedGUID, + _Out_ WINTUN_ADAPTER **Adapter, + _Inout_ BOOL *RebootRequired); + +WINSTATUS WINAPI +WintunDeleteAdapter(_In_ const WINTUN_ADAPTER *Adapter, _Inout_ BOOL *RebootRequired); + +typedef BOOL(CALLBACK *WINTUN_ENUMPROC)(_In_ const WINTUN_ADAPTER *Adapter, _In_ LPARAM Param); + +WINSTATUS WINAPI +WintunEnumAdapters(_In_z_count_c_(MAX_POOL) LPCWSTR Pool, _In_ WINTUN_ENUMPROC Func, _In_ LPARAM Param); diff --git a/api/api.vcxproj b/api/api.vcxproj index a940f35..b104d27 100644 --- a/api/api.vcxproj +++ b/api/api.vcxproj @@ -120,7 +120,7 @@ ..\$(WintunPlatform)\$(Configuration);%(AdditionalIncludeDirectories) - Bcrypt.lib;Setupapi.lib;%(AdditionalDependencies) + Bcrypt.lib;Cfgmgr32.lib;Iphlpapi.lib;Setupapi.lib;%(AdditionalDependencies) exports.def Windows @@ -162,6 +162,7 @@ + diff --git a/api/api.vcxproj.filters b/api/api.vcxproj.filters index 606c001..b418dd1 100644 --- a/api/api.vcxproj.filters +++ b/api/api.vcxproj.filters @@ -42,5 +42,8 @@ Source Files + + Source Files + \ No newline at end of file diff --git a/api/devmgmt.c b/api/devmgmt.c index bee7c8d..4325c2a 100644 --- a/api/devmgmt.c +++ b/api/devmgmt.c @@ -4,12 +4,15 @@ */ #include "api.h" +#include +#include #include #include #include #define WINTUN_HWID L"Wintun" #define WAIT_FOR_REGISTRY_TIMEOUT 10000 /* ms */ +#define MAX_POOL_DEVICE_TYPE (MAX_POOL + 0x100) const static GUID CLASS_NET_GUID = { 0x4d36e972L, 0xe325, 0x11ce, { 0xbf, 0xc1, 0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18 } }; const static GUID ADAPTER_NET_GUID = { 0xcac88484L, @@ -18,214 +21,33 @@ const static GUID ADAPTER_NET_GUID = { 0xcac88484L, { 0x82, 0xe6, 0x71, 0xa8, 0x7a, 0xba, 0xc3, 0x61 } }; /** - * Validate and/or sanitize string value read from registry. - * - * @param Buf On input, it contains pointer to pointer where the data is stored. The data must be - * allocated using HeapAlloc(GetProcessHeap(), 0). - * On output, it contains pointer to pointer where the sanitized data is stored. It must be - * released with HeapFree(GetProcessHeap(), 0, *Buf) after use. - * - * @param Len Length of data string in wide characters - * - * @param ValueType Type of data. Must be either REG_SZ or REG_EXPAND_SZ. - * - * @return ERROR_SUCCESS on success; Win32 error code otherwise - */ -static WINSTATUS -GetRegString(_Inout_ LPWSTR *Buf, _In_ DWORD Len, _In_ DWORD ValueType) -{ - HANDLE Heap = GetProcessHeap(); - - if (wcsnlen(*Buf, Len) >= Len) - { - /* String is missing zero-terminator. */ - LPWSTR BufZ = HeapAlloc(Heap, 0, ((size_t)Len + 1) * sizeof(WCHAR)); - if (!BufZ) - return ERROR_OUTOFMEMORY; - wmemcpy(BufZ, *Buf, Len); - BufZ[Len] = 0; - HeapFree(Heap, 0, *Buf); - *Buf = BufZ; - } - - if (ValueType != REG_EXPAND_SZ) - return ERROR_SUCCESS; - - /* ExpandEnvironmentStringsW() returns strlen on success or 0 on error. Bail out on empty input strings to - * disambiguate. */ - if (!(*Buf)[0]) - return ERROR_SUCCESS; - - Len = Len * 2 + 64; - for (;;) - { - LPWSTR Expanded = HeapAlloc(Heap, 0, Len * sizeof(WCHAR)); - if (!Expanded) - return ERROR_OUTOFMEMORY; - DWORD Result = ExpandEnvironmentStringsW(*Buf, Expanded, Len); - if (!Result) - { - Result = GetLastError(); - HeapFree(Heap, 0, Expanded); - return Result; - } - if (Result > Len) - { - HeapFree(Heap, 0, Expanded); - Len = Result; - continue; - } - HeapFree(Heap, 0, *Buf); - *Buf = Expanded; - return ERROR_SUCCESS; - } -} - -/** - * Validate and/or sanitize multi-string value read from registry. - * - * @param Buf On input, it contains pointer to pointer where the data is stored. The data must be - * allocated using HeapAlloc(GetProcessHeap(), 0). - * On output, it contains pointer to pointer where the sanitized data is stored. It must be - * released with HeapFree(GetProcessHeap(), 0, *Buf) after use. - * - * @param Len Length of data string in wide characters - * - * @param ValueType Type of data. Must be one of REG_MULTI_SZ, REG_SZ or REG_EXPAND_SZ. - * - * @return ERROR_SUCCESS on success; Win32 error code otherwise + * Returns the version of the Wintun driver and NDIS system currently loaded. */ -static WINSTATUS -GetRegMultiString(_Inout_ LPWSTR *Buf, _In_ DWORD Len, _In_ DWORD ValueType) +WINSTATUS WINAPI +WintunGetVersion( + _Out_ DWORD *DriverVersionMaj, + _Out_ DWORD *DriverVersionMin, + _Out_ DWORD *NdisVersionMaj, + _Out_ DWORD *NdisVersionMin) { - HANDLE Heap = GetProcessHeap(); - - if (ValueType == REG_MULTI_SZ) - { - for (size_t i = 0;; i += wcsnlen(*Buf + i, Len - i) + 1) - { - if (i > Len) - { - /* Missing string and list terminators. */ - LPWSTR BufZ = HeapAlloc(Heap, 0, ((size_t)Len + 2) * sizeof(WCHAR)); - if (!BufZ) - return ERROR_OUTOFMEMORY; - wmemcpy(BufZ, *Buf, Len); - BufZ[Len] = 0; - BufZ[Len + 1] = 0; - HeapFree(Heap, 0, *Buf); - *Buf = BufZ; - return ERROR_SUCCESS; - } - if (i == Len) - { - /* Missing list terminator. */ - LPWSTR BufZ = HeapAlloc(Heap, 0, ((size_t)Len + 1) * sizeof(WCHAR)); - if (!BufZ) - return ERROR_OUTOFMEMORY; - wmemcpy(BufZ, *Buf, Len); - BufZ[Len] = 0; - HeapFree(Heap, 0, *Buf); - *Buf = BufZ; - return ERROR_SUCCESS; - } - if (!(*Buf)[i]) - return ERROR_SUCCESS; - } - } - - /* Sanitize REG_SZ/REG_EXPAND_SZ and append a list terminator to make a multi-string. */ - DWORD Result = GetRegString(Buf, Len, ValueType); + HKEY Key; + DWORD Result = + RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SYSTEM\\CurrentControlSet\\Services\\Wintun", 0, KEY_QUERY_VALUE, &Key); if (Result != ERROR_SUCCESS) return Result; - Len = (DWORD)wcslen(*Buf) + 1; - LPWSTR BufZ = HeapAlloc(Heap, 0, ((size_t)Len + 1) * sizeof(WCHAR)); - if (!BufZ) - return ERROR_OUTOFMEMORY; - wmemcpy(BufZ, *Buf, Len); - BufZ[Len] = 0; - HeapFree(Heap, 0, *Buf); - *Buf = BufZ; - return ERROR_SUCCESS; -} - -/** - * Reads string value from registry key. - * - * @param Key Handle of the registry key to read from. Must be opened with read - * access. - * - * @param Name Name of the value to read - * - * @param Value Pointer to string to retrieve registry value. If the value type is - * REG_EXPAND_SZ the value is expanded using ExpandEnvironmentStrings(). - * The string must be released with HeapFree(GetProcessHeap(), 0, Value) - * after use. - * - * @return ERROR_SUCCESS on success; Win32 error code otherwise - */ -static WINSTATUS -RegQueryString(_In_ HKEY Key, _In_opt_z_ LPCWSTR Name, _Out_ LPWSTR *Value) -{ - HANDLE Heap = GetProcessHeap(); - DWORD Size = 256; - for (;;) - { - *Value = HeapAlloc(Heap, 0, Size); - if (!*Value) - return ERROR_OUTOFMEMORY; - DWORD ValueType; - DWORD Result = RegQueryValueExW(Key, Name, NULL, &ValueType, (BYTE *)*Value, &Size); - if (Result == ERROR_MORE_DATA) - { - HeapFree(Heap, 0, *Value); - continue; - } - if (Result != ERROR_SUCCESS) - { - HeapFree(Heap, 0, *Value); - return Result; - } - - switch (ValueType) - { - case REG_SZ: - case REG_EXPAND_SZ: - Result = GetRegString(Value, Size / sizeof(WCHAR), ValueType); - if (Result != ERROR_SUCCESS) - HeapFree(Heap, 0, *Value); - return Result; - default: - HeapFree(Heap, 0, *Value); - return ERROR_INVALID_DATATYPE; - } - } -} - -/** - * Reads a 32-bit DWORD value from registry key. - * - * @param Key Handle of the registry key to read from. Must be opened with read - * access. - * - * @param Name Name of the value to read - * - * @param Value Pointer to DWORD to retrieve registry value - * - * @return ERROR_SUCCESS on success; Win32 error code otherwise - */ -static WINSTATUS -RegQueryDWORD(_In_ HKEY Key, _In_opt_z_ LPCWSTR Name, _Out_ DWORD *Value) -{ - DWORD ValueType, Size = sizeof(DWORD); - DWORD Result = RegQueryValueExW(Key, Name, NULL, &ValueType, (BYTE *)Value, &Size); + Result = RegistryQueryDWORD(Key, L"DriverMajorVersion", DriverVersionMaj); if (Result != ERROR_SUCCESS) - return Result; - if (ValueType != REG_DWORD) - return ERROR_INVALID_DATATYPE; - if (Size != sizeof(DWORD)) - return ERROR_INVALID_DATA; - return ERROR_SUCCESS; + goto cleanupKey; + Result = RegistryQueryDWORD(Key, L"DriverMinorVersion", DriverVersionMin); + if (Result != ERROR_SUCCESS) + goto cleanupKey; + Result = RegistryQueryDWORD(Key, L"NdisMajorVersion", NdisVersionMaj); + if (Result != ERROR_SUCCESS) + goto cleanupKey; + Result = RegistryQueryDWORD(Key, L"NdisMinorVersion", NdisVersionMin); +cleanupKey: + RegCloseKey(Key); + return Result; } /** @@ -316,7 +138,8 @@ GetDeviceRegistryString( { case REG_SZ: case REG_EXPAND_SZ: - Result = GetRegString(PropertyBuffer, Size / sizeof(WCHAR), ValueType); + case REG_MULTI_SZ: + Result = RegistryGetString(PropertyBuffer, Size / sizeof(WCHAR), ValueType); if (Result != ERROR_SUCCESS) HeapFree(GetProcessHeap(), 0, *PropertyBuffer); return Result; @@ -358,7 +181,7 @@ GetDeviceRegistryMultiString( case REG_SZ: case REG_EXPAND_SZ: case REG_MULTI_SZ: - Result = GetRegMultiString(PropertyBuffer, Size / sizeof(WCHAR), ValueType); + Result = RegistryGetMultiString(PropertyBuffer, Size / sizeof(WCHAR), ValueType); if (Result != ERROR_SUCCESS) HeapFree(GetProcessHeap(), 0, *PropertyBuffer); return Result; @@ -372,27 +195,27 @@ GetDeviceRegistryMultiString( * Removes numbered suffix from adapter name. */ static void -RemoveNumberedSuffix(_In_z_ LPCWSTR IfName, _Out_ LPWSTR Removed) +RemoveNumberedSuffix(_In_z_ LPCWSTR Name, _Out_ LPWSTR Removed) { - size_t Len = wcslen(IfName); - if (Len && IfName[Len - 1] < L'0' || IfName[Len - 1] > L'9') + size_t Len = wcslen(Name); + if (Len && Name[Len - 1] < L'0' || Name[Len - 1] > L'9') { - wmemcpy(Removed, IfName, Len + 1); + wmemcpy(Removed, Name, Len + 1); return; } for (size_t i = Len; i--;) { - if (IfName[i] >= L'0' && IfName[i] <= L'9') + if (Name[i] >= L'0' && Name[i] <= L'9') continue; - if (IfName[i] == L' ') + if (Name[i] == L' ') { - wmemcpy(Removed, IfName, i); + wmemcpy(Removed, Name, i); Removed[i] = 0; return; } break; } - wmemcpy(Removed, IfName, Len + 1); + wmemcpy(Removed, Name, Len + 1); } /** @@ -414,24 +237,10 @@ IsOurHardwareID(_In_z_ LPWSTR Hwids) /** * Returns pool-specific device type name. */ -static WINSTATUS -GetPoolDeviceTypeName(_In_z_count_c_(MAX_POOL) LPCWSTR Pool, _Out_ LPWSTR *Name) +static void +GetPoolDeviceTypeName(_In_z_count_c_(MAX_POOL) LPCWSTR Pool, _Out_cap_c_(MAX_POOL_DEVICE_TYPE) LPWSTR Name) { - HANDLE Heap = GetProcessHeap(); - int Len = 256; - for (;;) - { - *Name = HeapAlloc(Heap, 0, Len * sizeof(WCHAR)); - if (!*Name) - return ERROR_OUTOFMEMORY; - if (_snwprintf_s(*Name, Len, _TRUNCATE, L"%s Tunnel", Pool) < 0) - { - HeapFree(Heap, 0, *Name); - Len *= 2; - continue; - } - return ERROR_SUCCESS; - } + _snwprintf_s(Name, MAX_POOL_DEVICE_TYPE, _TRUNCATE, L"%.*s Tunnel", MAX_POOL, Pool); } /** @@ -445,31 +254,28 @@ IsPoolMember( _Out_ BOOL *IsMember) { HANDLE Heap = GetProcessHeap(); - LPWSTR DeviceDesc, FriendlyName, PoolDeviceTypeName; + LPWSTR DeviceDesc, FriendlyName; DWORD Result = GetDeviceRegistryString(DevInfo, DevInfoData, SPDRP_DEVICEDESC, &DeviceDesc); if (Result != ERROR_SUCCESS) return Result; Result = GetDeviceRegistryString(DevInfo, DevInfoData, SPDRP_FRIENDLYNAME, &FriendlyName); if (Result != ERROR_SUCCESS) goto cleanupDeviceDesc; - Result = GetPoolDeviceTypeName(Pool, &PoolDeviceTypeName); - if (Result != ERROR_SUCCESS) - goto cleanupFriendlyName; + WCHAR PoolDeviceTypeName[MAX_POOL_DEVICE_TYPE]; + GetPoolDeviceTypeName(Pool, PoolDeviceTypeName); if (!_wcsicmp(FriendlyName, PoolDeviceTypeName) || !_wcsicmp(DeviceDesc, PoolDeviceTypeName)) { *IsMember = TRUE; - goto cleanupPoolDeviceTypeName; + goto cleanupFriendlyName; } RemoveNumberedSuffix(FriendlyName, FriendlyName); RemoveNumberedSuffix(DeviceDesc, DeviceDesc); if (!_wcsicmp(FriendlyName, PoolDeviceTypeName) || !_wcsicmp(DeviceDesc, PoolDeviceTypeName)) { *IsMember = TRUE; - goto cleanupPoolDeviceTypeName; + goto cleanupFriendlyName; } *IsMember = FALSE; -cleanupPoolDeviceTypeName: - HeapFree(Heap, 0, PoolDeviceTypeName); cleanupFriendlyName: HeapFree(Heap, 0, FriendlyName); cleanupDeviceDesc: @@ -502,7 +308,7 @@ GetDriverInfoDetail( _Out_ SP_DRVINFO_DETAIL_DATA_W **DriverDetailData) { HANDLE Heap = GetProcessHeap(); - DWORD Size = 2048; + DWORD Size = sizeof(SP_DRVINFO_DETAIL_DATA_W) + 0x100; for (;;) { *DriverDetailData = HeapAlloc(Heap, 0, Size); @@ -567,16 +373,49 @@ IsUsingOurDriver(_In_ HDEVINFO DevInfo, _In_ SP_DEVINFO_DATA *DevInfoData, _Out_ * * @param Pool Name of the adapter pool * - * @param Adapter A pointer to a Wintun adapter descriptor + * @param Adapter Pointer to a handle to receive the adapter descriptor. Must be released with + * HeapFree(GetProcessHeap(), 0, *Adapter). + * + * @return ERROR_SUCCESS on success; Win32 error code otherwise + */ +static WINSTATUS +GetNetCfgInstanceId(_In_ HDEVINFO DevInfo, _In_ SP_DEVINFO_DATA *DevInfoData, _Out_ GUID *CfgInstanceID) +{ + HKEY Key = SetupDiOpenDevRegKey(DevInfo, DevInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DRV, KEY_QUERY_VALUE); + if (Key == INVALID_HANDLE_VALUE) + return GetLastError(); + LPWSTR ValueStr; + DWORD Result = RegistryQueryString(Key, L"NetCfgInstanceId", &ValueStr); + if (Result != ERROR_SUCCESS) + goto cleanupKey; + Result = SUCCEEDED(CLSIDFromString(ValueStr, CfgInstanceID)) ? ERROR_SUCCESS : ERROR_INVALID_DATA; + HeapFree(GetProcessHeap(), 0, ValueStr); +cleanupKey: + RegCloseKey(Key); + return Result; +} + +/** + * Creates a Wintun interface descriptor and populates it from the device's registry key. + * + * @param DevInfo A handle to the device information set that contains a device information element that + * represents the device for which to open a registry key. + * + * @param DevInfoData A pointer to a structure that specifies the device information element in DeviceInfoSet. + * + * @param Pool Name of the adapter pool + * + * @param Adapter Pointer to a handle to receive the adapter descriptor. Must be released with + * HeapFree(GetProcessHeap(), 0, *Adapter). * * @return ERROR_SUCCESS on success; Win32 error code otherwise */ static WINSTATUS -InitAdapterData( +CreateAdapterData( _In_ HDEVINFO DevInfo, _In_ SP_DEVINFO_DATA *DevInfoData, _In_z_count_c_(MAX_POOL) LPCWSTR Pool, - _Out_ WINTUN_ADAPTER *Adapter) + _Out_ WINTUN_ADAPTER **Adapter) { DWORD Result; @@ -585,45 +424,117 @@ InitAdapterData( if (Key == INVALID_HANDLE_VALUE) return GetLastError(); + HANDLE Heap = GetProcessHeap(); + *Adapter = HeapAlloc(Heap, 0, sizeof(WINTUN_ADAPTER)); + if (!*Adapter) + { + Result = ERROR_OUTOFMEMORY; + goto cleanupKey; + } + /* Read the NetCfgInstanceId value and convert to GUID. */ LPWSTR ValueStr; - Result = RegQueryString(Key, L"NetCfgInstanceId", &ValueStr); + Result = RegistryQueryString(Key, L"NetCfgInstanceId", &ValueStr); if (Result != ERROR_SUCCESS) - goto cleanupKey; - if (FAILED(CLSIDFromString(ValueStr, &Adapter->CfgInstanceID))) + goto cleanupAdapter; + if (FAILED(CLSIDFromString(ValueStr, &(*Adapter)->CfgInstanceID))) { HeapFree(GetProcessHeap(), 0, ValueStr); Result = ERROR_INVALID_DATA; - goto cleanupKey; + goto cleanupAdapter; } HeapFree(GetProcessHeap(), 0, ValueStr); /* Read the NetLuidIndex value. */ - Result = RegQueryDWORD(Key, L"NetLuidIndex", &Adapter->LuidIndex); + Result = RegistryQueryDWORD(Key, L"NetLuidIndex", &(*Adapter)->LuidIndex); if (Result != ERROR_SUCCESS) - goto cleanupKey; + goto cleanupAdapter; /* Read the NetLuidIndex value. */ - Result = RegQueryDWORD(Key, L"*IfType", &Adapter->IfType); + Result = RegistryQueryDWORD(Key, L"*IfType", &(*Adapter)->IfType); if (Result != ERROR_SUCCESS) - goto cleanupKey; + goto cleanupAdapter; DWORD Size; if (!SetupDiGetDeviceInstanceIdW( - DevInfo, DevInfoData, Adapter->DevInstanceID, _countof(Adapter->DevInstanceID), &Size)) + DevInfo, DevInfoData, (*Adapter)->DevInstanceID, _countof((*Adapter)->DevInstanceID), &Size)) { Result = GetLastError(); - goto cleanupKey; + goto cleanupAdapter; } - wcscpy_s(Adapter->Pool, _countof(Adapter->Pool), Pool); + wcscpy_s((*Adapter)->Pool, _countof((*Adapter)->Pool), Pool); Result = ERROR_SUCCESS; +cleanupAdapter: + if (Result != ERROR_SUCCESS) + HeapFree(Heap, 0, *Adapter); cleanupKey: RegCloseKey(Key); return Result; } +/** + * Returns the device-level registry key path. + */ +static void +GetDeviceRegPath(_In_ const WINTUN_ADAPTER *Adapter, _Out_cap_c_(MAX_PATH + MAX_INSTANCE_ID) LPWSTR Path) +{ + _snwprintf_s( + Path, + MAX_PATH + MAX_INSTANCE_ID, + _TRUNCATE, + L"SYSTEM\\CurrentControlSet\\Enum\\%.*s", + MAX_INSTANCE_ID, + Adapter->DevInstanceID); +} + +/** + * Returns the adapter-specific TCP/IP network registry key path. + */ +static void +GetTcpipAdapterRegPath(_In_ const WINTUN_ADAPTER *Adapter, _Out_cap_c_(MAX_PATH) LPWSTR Path) +{ + WCHAR Guid[MAX_GUID_STRING_LEN]; + _snwprintf_s( + Path, + MAX_PATH, + _TRUNCATE, + L"SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\Adapters\\%.*s", + StringFromGUID2(&Adapter->CfgInstanceID, Guid, _countof(Guid)), + Guid); +} + +/** + * Returns the interface-specific TCP/IP network registry key path. + */ +static WINSTATUS +GetTcpipInterfaceRegPath(_In_ const WINTUN_ADAPTER *Adapter, _Out_cap_c_(MAX_PATH) LPWSTR Path) +{ + DWORD Result; + HKEY TcpipAdapterRegKey; + WCHAR TcpipAdapterRegPath[MAX_PATH]; + GetTcpipAdapterRegPath(Adapter, TcpipAdapterRegPath); + Result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, TcpipAdapterRegPath, 0, KEY_QUERY_VALUE, &TcpipAdapterRegKey); + if (Result != ERROR_SUCCESS) + return Result; + LPWSTR Paths; + Result = RegistryQueryString(TcpipAdapterRegKey, L"IpConfig", &Paths); + if (Result != ERROR_SUCCESS) + goto cleanupTcpipAdapterRegKey; + if (!Paths[0]) + { + Result = ERROR_NETWORK_NOT_AVAILABLE; + goto cleanupPaths; + } + _snwprintf_s(Path, MAX_PATH, _TRUNCATE, L"SYSTEM\\CurrentControlSet\\Services\\%s", Paths); +cleanupPaths: + HeapFree(GetProcessHeap(), 0, Paths); +cleanupTcpipAdapterRegKey: + RegCloseKey(TcpipAdapterRegKey); + return Result; +} + /** * Releases Wintun adapter resources. * @@ -640,7 +551,7 @@ WintunFreeAdapter(_In_ WINTUN_ADAPTER *Adapter) * * @param Pool Name of the adapter pool * - * @param IfName Adapter name + * @param Name Adapter name * * @param Adapter Pointer to a handle to receive the adapter handle. Must be released with * WintunFreeAdapter. @@ -650,7 +561,7 @@ WintunFreeAdapter(_In_ WINTUN_ADAPTER *Adapter) * ERROR_ALREADY_EXISTS if adapter is found but not a Wintun-class or not a member of the pool */ WINSTATUS WINAPI -WintunGetAdapter(_In_z_count_c_(MAX_POOL) LPCWSTR Pool, _In_z_ LPCWSTR IfName, _Out_ WINTUN_ADAPTER **Adapter) +WintunGetAdapter(_In_z_count_c_(MAX_POOL) LPCWSTR Pool, _In_z_ LPCWSTR Name, _Out_ WINTUN_ADAPTER **Adapter) { DWORD Result; HANDLE Mutex = TakeNameMutex(Pool); @@ -676,31 +587,17 @@ WintunGetAdapter(_In_z_count_c_(MAX_POOL) LPCWSTR Pool, _In_z_ LPCWSTR IfName, _ } GUID CfgInstanceID; - HKEY Key = SetupDiOpenDevRegKey(DevInfo, &DevInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DRV, KEY_QUERY_VALUE); - if (Key != INVALID_HANDLE_VALUE) - { - LPWSTR CfgInstanceIDStr; - Result = RegQueryString(Key, L"NetCfgInstanceId", &CfgInstanceIDStr); - if (Result == ERROR_SUCCESS) - { - Result = - SUCCEEDED(CLSIDFromString(CfgInstanceIDStr, &CfgInstanceID)) ? ERROR_SUCCESS : ERROR_INVALID_DATA; - HeapFree(Heap, 0, CfgInstanceIDStr); - } - RegCloseKey(Key); - } - else - Result = GetLastError(); + Result = GetNetCfgInstanceId(DevInfo, &DevInfoData, &CfgInstanceID); if (Result != ERROR_SUCCESS) continue; /* TODO: is there a better way than comparing ifnames? */ - WCHAR IfName2[0x400], IfName3[0x400]; /* TODO: Make dynamic. */ - if (NciGetConnectionName(&CfgInstanceID, IfName2, sizeof(IfName2), NULL) != ERROR_SUCCESS) + WCHAR Name2[MAX_ADAPTER_NAME], Name3[MAX_ADAPTER_NAME]; + if (NciGetConnectionName(&CfgInstanceID, Name2, sizeof(Name2), NULL) != ERROR_SUCCESS) continue; - IfName2[_countof(IfName2) - 1] = 0; - RemoveNumberedSuffix(IfName2, IfName3); - if (_wcsicmp(IfName, IfName2) && _wcsicmp(IfName, IfName3)) + Name2[_countof(Name2) - 1] = 0; + RemoveNumberedSuffix(Name2, Name3); + if (_wcsicmp(Name, Name2) && _wcsicmp(Name, Name3)) continue; /* Check the Hardware ID to make sure it's a real Wintun device. This avoids doing slow operations on non-Wintun @@ -737,21 +634,640 @@ WintunGetAdapter(_In_z_count_c_(MAX_POOL) LPCWSTR Pool, _In_z_ LPCWSTR IfName, _ goto cleanupDevInfo; } - *Adapter = HeapAlloc(Heap, 0, sizeof(WINTUN_ADAPTER)); - if (!*Adapter) + Result = CreateAdapterData(DevInfo, &DevInfoData, Pool, Adapter); + goto cleanupDevInfo; + } + Result = ERROR_FILE_NOT_FOUND; +cleanupDevInfo: + SetupDiDestroyDeviceInfoList(DevInfo); +cleanupMutex: + ReleaseNameMutex(Mutex); + return Result; +} + +/** + * Returns the name of the Wintun interface. + */ +WINSTATUS WINAPI +WintunGetAdapterName(_In_ const WINTUN_ADAPTER *Adapter, _Out_cap_c_(MAX_ADAPTER_NAME) LPWSTR Name) +{ + return NciGetConnectionName(&Adapter->CfgInstanceID, Name, MAX_ADAPTER_NAME * sizeof(WCHAR), NULL); +} + +static WINSTATUS +InterfaceGuidFromAlias(_In_z_ LPCWSTR Alias, _Out_ GUID *Guid) +{ + NET_LUID Luid; + DWORD Result = ConvertInterfaceAliasToLuid(Alias, &Luid); + if (Result != NO_ERROR) + return Result; + return ConvertInterfaceLuidToGuid(&Luid, Guid); +} + +/** + * Sets name of the Wintun interface. + */ +WINSTATUS WINAPI +WintunSetAdapterName(_In_ const WINTUN_ADAPTER *Adapter, _In_z_count_c_(MAX_ADAPTER_NAME) LPCWSTR Name) +{ + DWORD Result; + const int MaxSuffix = 1000; + WCHAR AvailableName[MAX_ADAPTER_NAME]; + wcscpy_s(AvailableName, _countof(AvailableName), Name); + for (int i = 0;; ++i) + { + Result = NciSetConnectionName(&Adapter->CfgInstanceID, AvailableName); + if (Result == ERROR_DUP_NAME) { - Result = ERROR_OUTOFMEMORY; - goto cleanupDevInfo; + GUID Guid2; + DWORD Result2 = InterfaceGuidFromAlias(AvailableName, &Guid2); + if (Result2 == ERROR_SUCCESS) + { + for (int j = 0; j < MaxSuffix; ++j) + { + WCHAR Proposal[MAX_ADAPTER_NAME]; + _snwprintf_s(Proposal, _countof(Proposal), _TRUNCATE, L"%.*s %d", MAX_ADAPTER_NAME, Name, j + 1); + if (_wcsnicmp(Proposal, AvailableName, MAX_ADAPTER_NAME) == 0) + continue; + Result2 = NciSetConnectionName(&Guid2, Proposal); + if (Result2 == ERROR_DUP_NAME) + continue; + if (Result2 == ERROR_SUCCESS) + { + Result = NciSetConnectionName(&Adapter->CfgInstanceID, AvailableName); + if (Result == ERROR_SUCCESS) + break; + } + break; + } + } } - Result = InitAdapterData(DevInfo, &DevInfoData, Pool, *Adapter); - if (Result != ERROR_SUCCESS) - HeapFree(Heap, 0, *Adapter); + if (Result == ERROR_SUCCESS) + break; + if (i > MaxSuffix || Result != ERROR_DUP_NAME) + return Result; + _snwprintf_s(AvailableName, _countof(AvailableName), _TRUNCATE, L"%.*s %d", MAX_ADAPTER_NAME, Name, i + 1); + } + + /* TODO: This should use NetSetup2 so that it doesn't get unset. */ + HKEY DeviceRegKey; + WCHAR DeviceRegPath[MAX_PATH + MAX_INSTANCE_ID]; + GetDeviceRegPath(Adapter, DeviceRegPath); + Result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, DeviceRegPath, 0, KEY_SET_VALUE, &DeviceRegKey); + if (Result != ERROR_SUCCESS) + return Result; + WCHAR PoolDeviceTypeName[MAX_POOL_DEVICE_TYPE]; + GetPoolDeviceTypeName(Adapter->Pool, PoolDeviceTypeName); + Result = RegSetKeyValueW( + DeviceRegKey, + NULL, + L"FriendlyName", + REG_SZ, + PoolDeviceTypeName, + (DWORD)((wcslen(PoolDeviceTypeName) + 1) * sizeof(WCHAR))); + RegCloseKey(DeviceRegKey); + return Result; +} + +/** + * Returns the GUID of the interface. + * + * @param Adapter Adapter handle obtained with WintunGetAdapter or WintunCreateAdapter + * + * @param Guid Pointer to GUID to receive adapter ID. + */ +void WINAPI +WintunGetAdapterGUID(_In_ const WINTUN_ADAPTER *Adapter, _Out_ GUID *Guid) +{ + memcpy_s(Guid, sizeof(*Guid), &Adapter->CfgInstanceID, sizeof(Adapter->CfgInstanceID)); +} + +/** + * Returns the LUID of the interface. + * + * @param Adapter Adapter handle obtained with WintunGetAdapter or WintunCreateAdapter + * + * @param Luid Pointer to LUID to receive adapter LUID. + */ +void WINAPI +WintunGetAdapterLUID(_In_ const WINTUN_ADAPTER *Adapter, _Out_ LUID *Luid) +{ + *(LONGLONG *)Luid = (((LONGLONG)Adapter->LuidIndex & ((1 << 24) - 1)) << 24) | + (((LONGLONG)Adapter->IfType & ((1 << 16) - 1)) << 48); +} + +/** + * Returns a handle to the adapter device object. + */ +WINSTATUS WINAPI +WintunGetAdapterDeviceObject(_In_ const WINTUN_ADAPTER *Adapter, _Out_ HANDLE *Handle) +{ + HANDLE Heap = GetProcessHeap(); + ULONG InterfacesLen; + DWORD Result = CM_Get_Device_Interface_List_SizeW( + &InterfacesLen, + (GUID *)&ADAPTER_NET_GUID, + (DEVINSTID_W)Adapter->DevInstanceID, + CM_GET_DEVICE_INTERFACE_LIST_PRESENT); + if (Result != CR_SUCCESS) + return Result; + LPWSTR Interfaces = HeapAlloc(Heap, 0, InterfacesLen * sizeof(WCHAR)); + if (!Interfaces) + return ERROR_OUTOFMEMORY; + Result = CM_Get_Device_Interface_ListW( + (GUID *)&ADAPTER_NET_GUID, + (DEVINSTID_W)Adapter->DevInstanceID, + Interfaces, + InterfacesLen, + CM_GET_DEVICE_INTERFACE_LIST_PRESENT); + if (Result != CR_SUCCESS) + goto cleanupBuf; + *Handle = CreateFileW( + Interfaces, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + 0, + NULL); + if (*Handle == INVALID_HANDLE_VALUE) + Result = GetLastError(); +cleanupBuf: + HeapFree(Heap, 0, Interfaces); + return Result; +} + +/** + * Sets device install parameters for a quiet installation. + */ +static WINSTATUS +SetQuietInstall(_In_ HDEVINFO DevInfo, _In_ SP_DEVINFO_DATA *DevInfoData) +{ + SP_DEVINSTALL_PARAMS_W DevInstallParams = { .cbSize = sizeof(SP_DEVINSTALL_PARAMS_W) }; + if (!SetupDiGetDeviceInstallParamsW(DevInfo, DevInfoData, &DevInstallParams)) + return GetLastError(); + DevInstallParams.Flags |= DI_QUIETINSTALL; + if (!SetupDiSetDeviceInstallParams(DevInfo, DevInfoData, &DevInstallParams)) + return GetLastError(); + return ERROR_SUCCESS; +} + +/** + * @return TRUE if DriverData date and version is newer than supplied parameters. + */ +static BOOL +IsNewer(_In_ const SP_DRVINFO_DATA_W *DriverData, _In_ const FILETIME *DriverDate, _In_ DWORDLONG DriverVersion) +{ + if (DriverData->DriverDate.dwHighDateTime > DriverDate->dwHighDateTime) + return TRUE; + if (DriverData->DriverDate.dwHighDateTime < DriverDate->dwHighDateTime) + return FALSE; + + if (DriverData->DriverDate.dwLowDateTime > DriverDate->dwLowDateTime) + return TRUE; + if (DriverData->DriverDate.dwLowDateTime < DriverDate->dwLowDateTime) + return FALSE; + + if (DriverData->DriverVersion > DriverVersion) + return TRUE; + if (DriverData->DriverVersion < DriverVersion) + return FALSE; + + return FALSE; +} + +/** + * Checks device install parameters if a system reboot is required. + */ +static BOOL +CheckReboot(_In_ HDEVINFO DevInfo, _In_ SP_DEVINFO_DATA *DevInfoData) +{ + SP_DEVINSTALL_PARAMS_W DevInstallParams = { .cbSize = sizeof(SP_DEVINSTALL_PARAMS_W) }; + if (!SetupDiGetDeviceInstallParamsW(DevInfo, DevInfoData, &DevInstallParams)) + return FALSE; + + return (DevInstallParams.Flags & (DI_NEEDREBOOT | DI_NEEDRESTART)) != 0; +} + +/** + * Creates a Wintun interface. + * + * @param Pool Name of the adapter pool + * + * @param Name The requested name of the interface + * + * @param RequestedGUID The GUID of the created network interface, which then influences NLA generation + * deterministically. If it is set to NULL, the GUID is chosen by the system at random, and hence + * a new NLA entry is created for each new interface. It is called "requested" GUID because the API + * it uses is completely undocumented, and so there could be minor interesting complications with + * its usage. + * + * @param Adapter Pointer to a handle to receive the adapter handle. Must be released with + * WintunFreeAdapter. + * + * @param RebootRequired Pointer to a boolean flag to be set to TRUE in case SetupAPI suggests a reboot. Must be + * initialised to FALSE manually before this function is called. + * + * @return ERROR_SUCCESS on success; Win32 error code otherwise + */ +WINSTATUS WINAPI +WintunCreateAdapter( + _In_z_count_c_(MAX_POOL) LPCWSTR Pool, + _In_z_ LPCWSTR Name, + _In_opt_ const GUID *RequestedGUID, + _Out_ WINTUN_ADAPTER **Adapter, + _Inout_ BOOL *RebootRequired) +{ + DWORD Result; + HANDLE Mutex = TakeNameMutex(Pool); + if (!Mutex) + return ERROR_GEN_FAILURE; + + HDEVINFO DevInfo = SetupDiCreateDeviceInfoListExW(&CLASS_NET_GUID, NULL, NULL, NULL); + if (DevInfo == INVALID_HANDLE_VALUE) + { + Result = GetLastError(); + goto cleanupMutex; + } + + WCHAR ClassName[MAX_CLASS_NAME_LEN]; + if (!SetupDiClassNameFromGuidExW(&CLASS_NET_GUID, ClassName, _countof(ClassName), NULL, NULL, NULL)) + { + Result = GetLastError(); goto cleanupDevInfo; } - Result = ERROR_FILE_NOT_FOUND; + + HANDLE Heap = GetProcessHeap(); + WCHAR PoolDeviceTypeName[MAX_POOL_DEVICE_TYPE]; + GetPoolDeviceTypeName(Pool, PoolDeviceTypeName); + SP_DEVINFO_DATA DevInfoData = { .cbSize = sizeof(SP_DEVINFO_DATA) }; + if (!SetupDiCreateDeviceInfoW( + DevInfo, ClassName, &CLASS_NET_GUID, PoolDeviceTypeName, NULL, DICD_GENERATE_ID, &DevInfoData)) + { + Result = GetLastError(); + goto cleanupDevInfo; + } + Result = SetQuietInstall(DevInfo, &DevInfoData); + if (Result != ERROR_SUCCESS) + goto cleanupDevInfo; + + if (!SetupDiSetSelectedDevice(DevInfo, &DevInfoData)) + { + Result = GetLastError(); + goto cleanupDevInfo; + } + + static const WCHAR Hwids[_countof(WINTUN_HWID) + 1 /*Multi-string terminator*/] = WINTUN_HWID; + if (!SetupDiSetDeviceRegistryPropertyW(DevInfo, &DevInfoData, SPDRP_HARDWAREID, (const BYTE *)Hwids, sizeof(Hwids))) + { + Result = GetLastError(); + goto cleanupDevInfo; + } + if (!SetupDiBuildDriverInfoList(DevInfo, &DevInfoData, SPDIT_COMPATDRIVER)) /* TODO: This takes ~510ms */ + { + Result = GetLastError(); + goto cleanupDevInfo; + } + + FILETIME DriverDate = { 0, 0 }; + DWORDLONG DriverVersion = 0; + for (DWORD DriverIndex = 0;; ++DriverIndex) /* TODO: This loop takes ~600ms */ + { + SP_DRVINFO_DATA_W DriverData = { .cbSize = sizeof(SP_DRVINFO_DATA_W) }; + if (!SetupDiEnumDriverInfoW(DevInfo, &DevInfoData, SPDIT_COMPATDRIVER, DriverIndex, &DriverData)) + { + if (GetLastError() == ERROR_NO_MORE_ITEMS) + break; + continue; + } + + /* Check the driver version first, since the check is trivial and will save us iterating over hardware IDs for + * any driver versioned prior our best match. */ + if (!IsNewer(&DriverData, &DriverDate, DriverVersion)) + continue; + + SP_DRVINFO_DETAIL_DATA_W *DriverDetailData; + if (GetDriverInfoDetail(DevInfo, &DevInfoData, &DriverData, &DriverDetailData) != ERROR_SUCCESS) + continue; + if ((DriverDetailData->CompatIDsOffset <= 1 || _wcsicmp(DriverDetailData->HardwareID, WINTUN_HWID)) && + (!DriverDetailData->CompatIDsLength || + !IsOurHardwareID(DriverDetailData->HardwareID + DriverDetailData->CompatIDsOffset))) + { + HeapFree(Heap, 0, DriverDetailData); + continue; + } + HeapFree(Heap, 0, DriverDetailData); + + if (!SetupDiSetSelectedDriverW(DevInfo, &DevInfoData, &DriverData)) + continue; + + DriverDate = DriverData.DriverDate; + DriverVersion = DriverData.DriverVersion; + } + + if (!DriverVersion) + { + Result = ERROR_FILE_NOT_FOUND; + goto cleanupDriverInfoList; + } + + if (!SetupDiCallClassInstaller(DIF_REGISTERDEVICE, DevInfo, &DevInfoData)) + { + Result = GetLastError(); + goto cleanupDevice; + } + SetupDiCallClassInstaller(DIF_REGISTER_COINSTALLERS, DevInfo, &DevInfoData); /* Ignore errors */ + + HKEY NetDevRegKey = INVALID_HANDLE_VALUE; + const int PollTimeout = 50 /* ms */; + for (int i = 0; NetDevRegKey == INVALID_HANDLE_VALUE && i < WAIT_FOR_REGISTRY_TIMEOUT / PollTimeout; ++i) + { + if (i) + Sleep(PollTimeout); + NetDevRegKey = SetupDiOpenDevRegKey( + DevInfo, &DevInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DRV, KEY_SET_VALUE | KEY_QUERY_VALUE | KEY_NOTIFY); + } + if (NetDevRegKey == INVALID_HANDLE_VALUE) + { + Result = GetLastError(); + goto cleanupDevice; + } + if (RequestedGUID) + { + WCHAR RequestedGUIDStr[MAX_GUID_STRING_LEN]; + Result = RegSetValueExW( + NetDevRegKey, + L"NetSetupAnticipatedInstanceId", + 0, + REG_SZ, + (const BYTE *)RequestedGUIDStr, + StringFromGUID2(RequestedGUID, RequestedGUIDStr, _countof(RequestedGUIDStr)) * sizeof(WCHAR)); + if (Result != ERROR_SUCCESS) + goto cleanupNetDevRegKey; + } + + SetupDiCallClassInstaller(DIF_INSTALLINTERFACES, DevInfo, &DevInfoData); /* Ignore errors */ + + if (!SetupDiCallClassInstaller(DIF_INSTALLDEVICE, DevInfo, &DevInfoData)) + { + Result = GetLastError(); + goto cleanupNetDevRegKey; + } + *RebootRequired = *RebootRequired || CheckReboot(DevInfo, &DevInfoData); + + if (!SetupDiSetDeviceRegistryPropertyW( + DevInfo, + &DevInfoData, + SPDRP_DEVICEDESC, + (const BYTE *)PoolDeviceTypeName, + (DWORD)(wcslen(PoolDeviceTypeName) * sizeof(WCHAR)))) + { + Result = GetLastError(); + goto cleanupNetDevRegKey; + } + + /* DIF_INSTALLDEVICE returns almost immediately, while the device installation continues in the background. It might + * take a while, before all registry keys and values are populated. */ + LPWSTR DummyStr; + Result = RegistryQueryStringWait(NetDevRegKey, L"NetCfgInstanceId", WAIT_FOR_REGISTRY_TIMEOUT, &DummyStr); + if (Result != ERROR_SUCCESS) + goto cleanupNetDevRegKey; + HeapFree(Heap, 0, DummyStr); + DWORD DummyDWORD; + Result = RegistryQueryDWORDWait(NetDevRegKey, L"NetLuidIndex", WAIT_FOR_REGISTRY_TIMEOUT, &DummyDWORD); + if (Result != ERROR_SUCCESS) + goto cleanupNetDevRegKey; + Result = RegistryQueryDWORDWait(NetDevRegKey, L"*IfType", WAIT_FOR_REGISTRY_TIMEOUT, &DummyDWORD); + if (Result != ERROR_SUCCESS) + goto cleanupNetDevRegKey; + + Result = CreateAdapterData(DevInfo, &DevInfoData, Pool, Adapter); + if (Result != ERROR_SUCCESS) + goto cleanupNetDevRegKey; + + HKEY TcpipAdapterRegKey; + WCHAR TcpipAdapterRegPath[MAX_PATH]; + GetTcpipAdapterRegPath(*Adapter, TcpipAdapterRegPath); + Result = RegistryOpenKeyWait( + HKEY_LOCAL_MACHINE, + TcpipAdapterRegPath, + KEY_QUERY_VALUE | KEY_NOTIFY, + WAIT_FOR_REGISTRY_TIMEOUT, + &TcpipAdapterRegKey); + if (Result != ERROR_SUCCESS) + goto cleanupAdapter; + Result = RegistryQueryStringWait(TcpipAdapterRegKey, L"IpConfig", WAIT_FOR_REGISTRY_TIMEOUT, &DummyStr); + if (Result != ERROR_SUCCESS) + goto cleanupTcpipAdapterRegKey; + HeapFree(Heap, 0, DummyStr); + + HKEY TcpipInterfaceRegKey; + WCHAR TcpipInterfaceRegPath[MAX_PATH]; + Result = GetTcpipInterfaceRegPath(*Adapter, TcpipInterfaceRegPath); + if (Result != ERROR_SUCCESS) + goto cleanupTcpipAdapterRegKey; + Result = RegistryOpenKeyWait( + HKEY_LOCAL_MACHINE, + TcpipInterfaceRegPath, + KEY_QUERY_VALUE | KEY_SET_VALUE, + WAIT_FOR_REGISTRY_TIMEOUT, + &TcpipInterfaceRegKey); + if (Result != ERROR_SUCCESS) + goto cleanupTcpipAdapterRegKey; + + const static DWORD EnableDeadGWDetect = 0; + RegSetKeyValueW( + TcpipInterfaceRegKey, + NULL, + L"EnableDeadGWDetect", + REG_DWORD, + &EnableDeadGWDetect, + sizeof(EnableDeadGWDetect)); /* Ignore errors */ + + Result = WintunSetAdapterName(*Adapter, Name); + RegCloseKey(TcpipInterfaceRegKey); +cleanupTcpipAdapterRegKey: + RegCloseKey(TcpipAdapterRegKey); +cleanupAdapter: + if (Result != ERROR_SUCCESS) + HeapFree(Heap, 0, *Adapter); +cleanupNetDevRegKey: + RegCloseKey(NetDevRegKey); +cleanupDevice: + if (Result != ERROR_SUCCESS) + { + /* The interface failed to install, or the interface ID was unobtainable. Clean-up. */ + SP_REMOVEDEVICE_PARAMS RemoveDeviceParams = { .ClassInstallHeader = { .cbSize = sizeof(SP_CLASSINSTALL_HEADER), + .InstallFunction = DIF_REMOVE }, + .Scope = DI_REMOVEDEVICE_GLOBAL }; + if (SetupDiSetClassInstallParamsW( + DevInfo, &DevInfoData, &RemoveDeviceParams.ClassInstallHeader, sizeof(RemoveDeviceParams)) && + SetupDiCallClassInstaller(DIF_REMOVE, DevInfo, &DevInfoData)) + *RebootRequired = *RebootRequired || CheckReboot(DevInfo, &DevInfoData); + } +cleanupDriverInfoList: + SetupDiDestroyDriverInfoList(DevInfo, &DevInfoData, SPDIT_COMPATDRIVER); cleanupDevInfo: SetupDiDestroyDeviceInfoList(DevInfo); cleanupMutex: ReleaseNameMutex(Mutex); return Result; } + +/** + * Returns TUN device info list handle and interface device info data. The device info list handle must be closed after + * use. In case the device is not found, ERROR_OBJECT_NOT_FOUND is returned. + */ +static WINSTATUS +GetDevInfoData(_In_ const GUID *CfgInstanceID, _Out_ HDEVINFO *DevInfo, _Out_ SP_DEVINFO_DATA *DevInfoData) +{ + DWORD Result; + *DevInfo = SetupDiGetClassDevsExW(&CLASS_NET_GUID, NULL, NULL, DIGCF_PRESENT, NULL, NULL, NULL); + if (!*DevInfo) + return GetLastError(); + for (DWORD MemberIndex = 0;; ++MemberIndex) + { + DevInfoData->cbSize = sizeof(SP_DEVINFO_DATA); + if (!SetupDiEnumDeviceInfo(*DevInfo, MemberIndex, DevInfoData)) + { + Result = GetLastError(); + if (Result == ERROR_NO_MORE_ITEMS) + break; + continue; + } + + GUID CfgInstanceID2; + Result = GetNetCfgInstanceId(*DevInfo, DevInfoData, &CfgInstanceID2); + if (Result != ERROR_SUCCESS || memcmp(CfgInstanceID, &CfgInstanceID2, sizeof(GUID)) != 0) + continue; + + Result = SetQuietInstall(*DevInfo, DevInfoData); + if (Result != ERROR_SUCCESS) + { + SetupDiDestroyDeviceInfoList(*DevInfo); + return Result; + } + return ERROR_SUCCESS; + } + SetupDiDestroyDeviceInfoList(*DevInfo); + return ERROR_OBJECT_NOT_FOUND; +} + +/** + * Deletes a Wintun interface. + * + * @param Adapter Adapter handle obtained with WintunGetAdapter or WintunCreateAdapter + * + * @param RebootRequired Pointer to a boolean flag to be set to TRUE in case SetupAPI suggests a reboot. Must be + * initialised to FALSE manually before this function is called. + * + * @return ERROR_SUCCESS on success; Win32 error code otherwise. This function succeeds if the interface was not found. + */ +WINSTATUS WINAPI +WintunDeleteAdapter(_In_ const WINTUN_ADAPTER *Adapter, _Inout_ BOOL *RebootRequired) +{ + HDEVINFO DevInfo; + SP_DEVINFO_DATA DevInfoData; + DWORD Result = GetDevInfoData(&Adapter->CfgInstanceID, &DevInfo, &DevInfoData); + if (Result == ERROR_OBJECT_NOT_FOUND) + return ERROR_SUCCESS; + if (Result != ERROR_SUCCESS) + return Result; + SP_REMOVEDEVICE_PARAMS RemoveDeviceParams = { .ClassInstallHeader = { .cbSize = sizeof(SP_CLASSINSTALL_HEADER), + .InstallFunction = DIF_REMOVE }, + .Scope = DI_REMOVEDEVICE_GLOBAL }; + if (SetupDiSetClassInstallParamsW( + DevInfo, &DevInfoData, &RemoveDeviceParams.ClassInstallHeader, sizeof(RemoveDeviceParams)) && + SetupDiCallClassInstaller(DIF_REMOVE, DevInfo, &DevInfoData)) + *RebootRequired = *RebootRequired || CheckReboot(DevInfo, &DevInfoData); + else + Result = GetLastError(); + SetupDiDestroyDeviceInfoList(DevInfo); + return Result; +} + +/** + * Enumerates all Wintun adapters. + * + * @param Pool Name of the adapter pool + * + * @param Func Callback function. To continue enumeration, the callback function must return TRUE; to stop + * enumeration, it must return FALSE. + * + * @param Param An application-defined value to be passed to the callback function + * + * @return ERROR_SUCCESS on success; Win32 error code otherwise + */ +WINSTATUS WINAPI +WintunEnumAdapters(_In_z_count_c_(MAX_POOL) LPCWSTR Pool, _In_ WINTUN_ENUMPROC Func, _In_ LPARAM Param) +{ + DWORD Result; + HANDLE Mutex = TakeNameMutex(Pool); + if (!Mutex) + return ERROR_GEN_FAILURE; + HDEVINFO DevInfo = SetupDiGetClassDevsExW(&CLASS_NET_GUID, NULL, NULL, DIGCF_PRESENT, NULL, NULL, NULL); + if (DevInfo == INVALID_HANDLE_VALUE) + { + Result = GetLastError(); + goto cleanupMutex; + } + HANDLE Heap = GetProcessHeap(); + for (DWORD MemberIndex = 0;; ++MemberIndex) + { + SP_DEVINFO_DATA DevInfoData = { .cbSize = sizeof(SP_DEVINFO_DATA) }; + if (!SetupDiEnumDeviceInfo(DevInfo, MemberIndex, &DevInfoData)) + { + if (GetLastError() == ERROR_NO_MORE_ITEMS) + { + Result = ERROR_SUCCESS; + break; + } + continue; + } + + /* Check the Hardware ID to make sure it's a real Wintun device. This avoids doing slow operations on non-Wintun + * devices. */ + LPWSTR Hwids; + Result = GetDeviceRegistryMultiString(DevInfo, &DevInfoData, SPDRP_HARDWAREID, &Hwids); + if (Result != ERROR_SUCCESS) + break; + if (!IsOurHardwareID(Hwids)) + { + HeapFree(Heap, 0, Hwids); + continue; + } + HeapFree(Heap, 0, Hwids); + + BOOL IsOurDriver; + Result = IsUsingOurDriver(DevInfo, &DevInfoData, &IsOurDriver); + if (Result != ERROR_SUCCESS) + break; + if (!IsOurDriver) + continue; + + BOOL IsMember; + Result = IsPoolMember(Pool, DevInfo, &DevInfoData, &IsMember); + if (Result != ERROR_SUCCESS) + break; + if (!IsMember) + continue; + + SetQuietInstall(DevInfo, &DevInfoData); /* Ignore errors */ + + WINTUN_ADAPTER *Adapter; + Result = CreateAdapterData(DevInfo, &DevInfoData, Pool, &Adapter); + if (Result != ERROR_SUCCESS) + break; + if (Func(Adapter, Param)) + HeapFree(Heap, 0, Adapter); + else + { + HeapFree(Heap, 0, Adapter); + break; + } + } + SetupDiDestroyDeviceInfoList(DevInfo); +cleanupMutex: + ReleaseNameMutex(Mutex); + return Result; +} diff --git a/api/exports.def b/api/exports.def index 83903fc..c872b41 100644 --- a/api/exports.def +++ b/api/exports.def @@ -1,3 +1,12 @@ EXPORTS WintunFreeAdapter WintunGetAdapter + WintunGetAdapterName + WintunSetAdapterName + WintunGetAdapterGUID + WintunGetAdapterLUID + WintunGetAdapterDeviceObject + WintunGetVersion + WintunCreateAdapter + WintunDeleteAdapter + WintunEnumAdapters diff --git a/api/registry.c b/api/registry.c new file mode 100644 index 0000000..52a3d66 --- /dev/null +++ b/api/registry.c @@ -0,0 +1,411 @@ +/* SPDX-License-Identifier: GPL-2.0 + * + * Copyright (C) 2018-2020 WireGuard LLC. All Rights Reserved. + */ + +#include "api.h" +#include +#include + +static WINSTATUS +OpenKeyWait(_In_ HKEY Key, _Inout_z_ LPWSTR Path, _In_ DWORD Access, _In_ ULONGLONG Deadline, _Out_ HKEY *KeyOut) +{ + DWORD Result; + LPWSTR PathNext = wcschr(Path, L'\\'); + if (PathNext) + *PathNext = 0; + + HANDLE Event = CreateEventW(NULL, FALSE, FALSE, NULL); + if (!Event) + return GetLastError(); + for (;;) + { + Result = RegNotifyChangeKeyValue(Key, FALSE, REG_NOTIFY_CHANGE_NAME, Event, TRUE); + if (Result != ERROR_SUCCESS) + break; + + HKEY Subkey; + Result = RegOpenKeyExW(Key, Path, 0, PathNext ? KEY_NOTIFY : Access, &Subkey); + if (Result == ERROR_SUCCESS) + { + if (PathNext) + { + Result = OpenKeyWait(Subkey, PathNext + 1, Access, Deadline, KeyOut); + RegCloseKey(Subkey); + } + else + *KeyOut = Subkey; + break; + } + if (Result != ERROR_FILE_NOT_FOUND && Result != ERROR_PATH_NOT_FOUND) + break; + + LONGLONG TimeLeft = Deadline - GetTickCount64(); + if (TimeLeft < 0) + TimeLeft = 0; + if (WaitForSingleObject(Event, (DWORD)TimeLeft) != WAIT_OBJECT_0) + break; + } + CloseHandle(Event); + return Result; +} + +/** + * Opens the specified registry key. It waits for the registry key to become available. + * + * @param Key Handle of the parent registry key. Must be opened with notify access. + * + * @param Path Subpath of the registry key to open + * + * @param Access A mask that specifies the desired access rights to the key to be opened + * + * @param Timeout Timeout to wait for the value in milliseconds + * + * @param KeyOut Pointer to a variable to receive the key handle + * + * @return ERROR_SUCCESS on success; WAIT_TIMEOUT on timeout; error code otherwise + */ +WINSTATUS +RegistryOpenKeyWait( + _In_ HKEY Key, + _In_z_count_c_(MAX_PATH) LPCWSTR Path, + _In_ DWORD Access, + _In_ DWORD Timeout, + _Out_ HKEY *KeyOut) +{ + WCHAR Buf[MAX_PATH]; + wcscpy_s(Buf, _countof(Buf), Path); + return OpenKeyWait(Key, Buf, Access, GetTickCount64() + Timeout, KeyOut); +} + +WINSTATUS +RegistryWaitForKey(_In_ HKEY Key, _In_z_count_c_(MAX_PATH) LPCWSTR Path, _In_ DWORD Timeout) +{ + HKEY k; + DWORD Result = RegistryOpenKeyWait(Key, Path, KEY_NOTIFY, Timeout, &k); + if (Result != ERROR_SUCCESS) + return Result; + RegCloseKey(k); + return ERROR_SUCCESS; +} + +/** + * Validate and/or sanitize string value read from registry. + * + * @param Buf On input, it contains pointer to pointer where the data is stored. The data must be + * allocated using HeapAlloc(GetProcessHeap(), 0). + * On output, it contains pointer to pointer where the sanitized data is stored. It must be + * released with HeapFree(GetProcessHeap(), 0, *Buf) after use. + * + * @param Len Length of data string in wide characters + * + * @param ValueType Type of data. Must be either REG_SZ or REG_EXPAND_SZ. REG_MULTI_SZ is treated like REG_SZ; only + * the first string of a multi-string is to be used. + * + * @return ERROR_SUCCESS on success; Win32 error code otherwise + */ +WINSTATUS +RegistryGetString(_Inout_ LPWSTR *Buf, _In_ DWORD Len, _In_ DWORD ValueType) +{ + HANDLE Heap = GetProcessHeap(); + + if (wcsnlen(*Buf, Len) >= Len) + { + /* String is missing zero-terminator. */ + LPWSTR BufZ = HeapAlloc(Heap, 0, ((size_t)Len + 1) * sizeof(WCHAR)); + if (!BufZ) + return ERROR_OUTOFMEMORY; + wmemcpy(BufZ, *Buf, Len); + BufZ[Len] = 0; + HeapFree(Heap, 0, *Buf); + *Buf = BufZ; + } + + if (ValueType != REG_EXPAND_SZ) + return ERROR_SUCCESS; + + /* ExpandEnvironmentStringsW() returns strlen on success or 0 on error. Bail out on empty input strings to + * disambiguate. */ + if (!(*Buf)[0]) + return ERROR_SUCCESS; + + Len = Len * 2 + 64; + for (;;) + { + LPWSTR Expanded = HeapAlloc(Heap, 0, Len * sizeof(WCHAR)); + if (!Expanded) + return ERROR_OUTOFMEMORY; + DWORD Result = ExpandEnvironmentStringsW(*Buf, Expanded, Len); + if (!Result) + { + Result = GetLastError(); + HeapFree(Heap, 0, Expanded); + return Result; + } + if (Result > Len) + { + HeapFree(Heap, 0, Expanded); + Len = Result; + continue; + } + HeapFree(Heap, 0, *Buf); + *Buf = Expanded; + return ERROR_SUCCESS; + } +} + +/** + * Validate and/or sanitize multi-string value read from registry. + * + * @param Buf On input, it contains pointer to pointer where the data is stored. The data must be + * allocated using HeapAlloc(GetProcessHeap(), 0). + * On output, it contains pointer to pointer where the sanitized data is stored. It must be + * released with HeapFree(GetProcessHeap(), 0, *Buf) after use. + * + * @param Len Length of data string in wide characters + * + * @param ValueType Type of data. Must be one of REG_MULTI_SZ, REG_SZ or REG_EXPAND_SZ. + * + * @return ERROR_SUCCESS on success; Win32 error code otherwise + */ +WINSTATUS +RegistryGetMultiString(_Inout_ LPWSTR *Buf, _In_ DWORD Len, _In_ DWORD ValueType) +{ + HANDLE Heap = GetProcessHeap(); + + if (ValueType == REG_MULTI_SZ) + { + for (size_t i = 0;; i += wcsnlen(*Buf + i, Len - i) + 1) + { + if (i > Len) + { + /* Missing string and list terminators. */ + LPWSTR BufZ = HeapAlloc(Heap, 0, ((size_t)Len + 2) * sizeof(WCHAR)); + if (!BufZ) + return ERROR_OUTOFMEMORY; + wmemcpy(BufZ, *Buf, Len); + BufZ[Len] = 0; + BufZ[Len + 1] = 0; + HeapFree(Heap, 0, *Buf); + *Buf = BufZ; + return ERROR_SUCCESS; + } + if (i == Len) + { + /* Missing list terminator. */ + LPWSTR BufZ = HeapAlloc(Heap, 0, ((size_t)Len + 1) * sizeof(WCHAR)); + if (!BufZ) + return ERROR_OUTOFMEMORY; + wmemcpy(BufZ, *Buf, Len); + BufZ[Len] = 0; + HeapFree(Heap, 0, *Buf); + *Buf = BufZ; + return ERROR_SUCCESS; + } + if (!(*Buf)[i]) + return ERROR_SUCCESS; + } + } + + /* Sanitize REG_SZ/REG_EXPAND_SZ and append a list terminator to make a multi-string. */ + DWORD Result = RegistryGetString(Buf, Len, ValueType); + if (Result != ERROR_SUCCESS) + return Result; + Len = (DWORD)wcslen(*Buf) + 1; + LPWSTR BufZ = HeapAlloc(Heap, 0, ((size_t)Len + 1) * sizeof(WCHAR)); + if (!BufZ) + return ERROR_OUTOFMEMORY; + wmemcpy(BufZ, *Buf, Len); + BufZ[Len] = 0; + HeapFree(Heap, 0, *Buf); + *Buf = BufZ; + return ERROR_SUCCESS; +} + +/** + * Retrieves the type and data for the specified value name associated with an open registry key. + * + * @param Key Handle of the registry key to read from. Must be opened with read + * access. + * + * @param Name Name of the value to read + * + * @param ValueType A pointer to a variable that receives a code indicating the type of data stored in the specified + * value. + * + * @param Buf Pointer to a pointer to retrieve registry value. The buffer must be released with + * HeapFree(GetProcessHeap(), 0, *Buf) after use. + * + * @param BufLen On input, a hint of expected registry value size in bytes; on output, actual registry value size + * in bytes. + * + * @return ERROR_SUCCESS on success; Win32 error code otherwise + */ +static WINSTATUS +RegistryQuery( + _In_ HKEY Key, + _In_opt_z_ LPCWSTR Name, + _Out_opt_ DWORD *ValueType, + _Out_ void **Buf, + _Inout_ DWORD *BufLen) +{ + HANDLE Heap = GetProcessHeap(); + for (;;) + { + *Buf = HeapAlloc(Heap, 0, *BufLen); + if (!*Buf) + return ERROR_OUTOFMEMORY; + LSTATUS Result = RegQueryValueExW(Key, Name, NULL, ValueType, (BYTE *)*Buf, BufLen); + if (Result == ERROR_SUCCESS) + return ERROR_SUCCESS; + HeapFree(Heap, 0, *Buf); + if (Result != ERROR_MORE_DATA) + return Result; + } +} + +/** + * Reads string value from registry key. + * + * @param Key Handle of the registry key to read from. Must be opened with read + * access. + * + * @param Name Name of the value to read + * + * @param Value Pointer to string to retrieve registry value. If the value type is + * REG_EXPAND_SZ the value is expanded using ExpandEnvironmentStrings(). If the value type is + * REG_MULTI_SZ, only the first string from the multi-string is returned. + * The string must be released with HeapFree(GetProcessHeap(), 0, Value) after use. + * + * @return ERROR_SUCCESS on success; Win32 error code otherwise + */ +WINSTATUS +RegistryQueryString(_In_ HKEY Key, _In_opt_z_ LPCWSTR Name, _Out_ LPWSTR *Value) +{ + DWORD ValueType, Size = 256 * sizeof(WCHAR); + DWORD Result = RegistryQuery(Key, Name, &ValueType, Value, &Size); + if (Result != ERROR_SUCCESS) + return Result; + switch (ValueType) + { + case REG_SZ: + case REG_EXPAND_SZ: + case REG_MULTI_SZ: + Result = RegistryGetString(Value, Size / sizeof(WCHAR), ValueType); + if (Result != ERROR_SUCCESS) + HeapFree(GetProcessHeap(), 0, *Value); + return Result; + default: + HeapFree(GetProcessHeap(), 0, *Value); + return ERROR_INVALID_DATATYPE; + } +} + +/** + * Reads string value from registry key. It waits for the registry value to become available. + * + * @param Key Handle of the registry key to read from. Must be opened with read and notify + * access. + * + * @param Name Name of the value to read + * + * @param Timeout Timeout to wait for the value in milliseconds + * + * @param Value Pointer to string to retrieve registry value. If the value type is + * REG_EXPAND_SZ the value is expanded using ExpandEnvironmentStrings(). + * The string must be released with HeapFree(GetProcessHeap(), 0, Value) + * after use. + * + * @return ERROR_SUCCESS on success; Win32 error code otherwise + */ +WINSTATUS +RegistryQueryStringWait(_In_ HKEY Key, _In_opt_z_ LPCWSTR Name, _In_ DWORD Timeout, _Out_ LPWSTR *Value) +{ + DWORD Result; + ULONGLONG Deadline = GetTickCount64() + Timeout; + HANDLE Event = CreateEventW(NULL, FALSE, FALSE, NULL); + if (!Event) + return GetLastError(); + for (;;) + { + Result = RegNotifyChangeKeyValue(Key, FALSE, REG_NOTIFY_CHANGE_LAST_SET, Event, TRUE); + if (Result != ERROR_SUCCESS) + break; + Result = RegistryQueryString(Key, Name, Value); + if (Result != ERROR_FILE_NOT_FOUND && Result != ERROR_PATH_NOT_FOUND) + break; + LONGLONG TimeLeft = Deadline - GetTickCount64(); + if (TimeLeft < 0) + TimeLeft = 0; + if (WaitForSingleObject(Event, (DWORD)TimeLeft) != WAIT_OBJECT_0) + break; + } + CloseHandle(Event); + return Result; +} + +/** + * Reads a 32-bit DWORD value from registry key. + * + * @param Key Handle of the registry key to read from. Must be opened with read + * access. + * + * @param Name Name of the value to read + * + * @param Value Pointer to DWORD to retrieve registry value + * + * @return ERROR_SUCCESS on success; Win32 error code otherwise + */ +WINSTATUS +RegistryQueryDWORD(_In_ HKEY Key, _In_opt_z_ LPCWSTR Name, _Out_ DWORD *Value) +{ + DWORD ValueType, Size = sizeof(DWORD); + DWORD Result = RegQueryValueExW(Key, Name, NULL, &ValueType, (BYTE *)Value, &Size); + if (Result != ERROR_SUCCESS) + return Result; + if (ValueType != REG_DWORD) + return ERROR_INVALID_DATATYPE; + if (Size != sizeof(DWORD)) + return ERROR_INVALID_DATA; + return ERROR_SUCCESS; +} + +/** + * Reads a 32-bit DWORD value from registry key. It waits for the registry value to become available. + * + * @param Key Handle of the registry key to read from. Must be opened with read + * access. + * + * @param Name Name of the value to read + * + * @param Timeout Timeout to wait for the value in milliseconds + * + * @param Value Pointer to DWORD to retrieve registry value + * + * @return ERROR_SUCCESS on success; Win32 error code otherwise + */ +WINSTATUS +RegistryQueryDWORDWait(_In_ HKEY Key, _In_opt_z_ LPCWSTR Name, _In_ DWORD Timeout, _Out_ DWORD *Value) +{ + DWORD Result; + ULONGLONG Deadline = GetTickCount64() + Timeout; + HANDLE Event = CreateEventW(NULL, FALSE, FALSE, NULL); + if (!Event) + return GetLastError(); + for (;;) + { + Result = RegNotifyChangeKeyValue(Key, FALSE, REG_NOTIFY_CHANGE_LAST_SET, Event, TRUE); + if (Result != ERROR_SUCCESS) + break; + Result = RegistryQueryDWORD(Key, Name, Value); + if (Result != ERROR_FILE_NOT_FOUND && Result != ERROR_PATH_NOT_FOUND) + break; + LONGLONG TimeLeft = Deadline - GetTickCount64(); + if (TimeLeft < 0) + TimeLeft = 0; + if (WaitForSingleObject(Event, (DWORD)TimeLeft) != WAIT_OBJECT_0) + break; + } + CloseHandle(Event); + return Result; +} -- cgit v1.2.3-59-g8ed1b