0
0
mirror of https://github.com/OpenVPN/openvpn3.git synced 2024-09-20 04:02:15 +02:00

add Windows Registry operations abstraction layer

Create a struct Reg, which contains various setter and getter functions
for different registry types and other operations that will be used.
This is done so that these operations can be injected as a dependency
and thus replaced with mock operation for the purpose of testing.
Besides that it makes code more brief and less error prone, since
there's now one implementation for converting C <-> C++ for each operation.

Move existing class RegKey and class RegKeyEnumerator into struct Reg as
well, so they are now known as Reg::Key and Reg::KeyEnumerator.

Signed-off-by: Heiko Hund <heiko@openvpn.net>
This commit is contained in:
Heiko Hund 2024-04-24 00:27:57 +02:00
parent 08d5438742
commit bc24b7c80d
4 changed files with 382 additions and 94 deletions

View File

@ -47,7 +47,7 @@ class NRPT
static void create_rule(const std::vector<std::string> names, const std::vector<std::string> dns_servers)
{
Win::RegKey key;
Win::Reg::Key key;
for (size_t i = 0; i < names.size(); ++i)
{
@ -114,16 +114,17 @@ class NRPT
static bool delete_rule()
{
Win::RegKeyEnumerator keys(HKEY_LOCAL_MACHINE, dnsPolicyConfig());
Reg::Key policies(wstring::from_utf8(dnsPolicyConfig()));
Win::Reg::KeyEnumerator keys(policies);
for (const auto &key : keys)
{
// remove only own policies
if (key.find(policyPrefix()) == std::string::npos)
if (key.find(wstring::from_utf8(policyPrefix())) == std::wstring::npos)
continue;
std::ostringstream ss;
ss << dnsPolicyConfig() << "\\" << key;
ss << dnsPolicyConfig() << "\\" << wstring::to_utf8(key);
auto path = ss.str();
::RegDeleteTreeA(HKEY_LOCAL_MACHINE, path.c_str());
}

View File

@ -135,7 +135,7 @@ inline std::vector<TapGuidLuid> tap_guids(const Type tun_type)
break;
}
Win::RegKey adapter_key;
Win::Reg::Key adapter_key;
status = ::RegOpenKeyExA(HKEY_LOCAL_MACHINE,
ADAPTER,
0,
@ -150,7 +150,7 @@ inline std::vector<TapGuidLuid> tap_guids(const Type tun_type)
for (int i = 0;; ++i)
{
char strbuf[256];
Win::RegKey unit_key;
Win::Reg::Key unit_key;
len = sizeof(strbuf);
status = ::RegEnumKeyExA(adapter_key(),
@ -300,7 +300,7 @@ struct TapNameGuidPairList : public std::vector<TapNameGuidPair>
DWORD len;
DWORD data_type;
Win::RegKey network_connections_key;
Win::Reg::Key network_connections_key;
status = ::RegOpenKeyExA(HKEY_LOCAL_MACHINE,
NETWORK_CONNECTIONS,
0,
@ -315,7 +315,7 @@ struct TapNameGuidPairList : public std::vector<TapNameGuidPair>
for (int i = 0;; ++i)
{
char strbuf[256];
Win::RegKey connection_key;
Win::Reg::Key connection_key;
len = sizeof(strbuf);
status = ::RegEnumKeyExA(network_connections_key(),
@ -463,7 +463,7 @@ struct DeviceInstanceIdInterfaceList : public std::vector<DeviceInstanceIdInterf
continue;
}
Win::RegKey regkey;
Win::Reg::Key regkey;
*regkey.ref() = SetupDiOpenDevRegKey(device_info_set, &dev_info_data, DICS_FLAG_GLOBAL, 0, DIREG_DRV, KEY_QUERY_VALUE);
if (!regkey.defined())
continue;
@ -922,7 +922,7 @@ class ActionSetAdapterDomainSuffix : public Action
os << to_string() << std::endl;
LONG status;
Win::RegKey key;
Win::Reg::Key key;
const std::string reg_key_name = "SYSTEM\\CurrentControlSet\\services\\Tcpip\\Parameters\\Interfaces\\" + tap_guid;
status = ::RegOpenKeyExA(HKEY_LOCAL_MACHINE,
reg_key_name.c_str(),

View File

@ -49,8 +49,8 @@ class WinProxySettings : public ProxySettings
Impersonate imp{false};
LONG status;
RegKey hkcu;
RegKey key;
Reg::Key hkcu;
Reg::Key key;
status = ::RegOpenCurrentUser(KEY_QUERY_VALUE | KEY_SET_VALUE, hkcu.ref());
check_reg_error<proxy_error>(status, "RegOpenCurrentUser");
@ -80,14 +80,14 @@ class WinProxySettings : public ProxySettings
}
private:
void restore_key(Win::RegKey &regkey, const std::string &key, bool str)
void restore_key(Win::Reg::Key &regkey, const std::string &key, bool str)
{
LONG status;
char prev_val_str[1024] = {0}; // should be enough to fit proxy URL
DWORD prev_val_dword;
DWORD prev_buf_size = str ? sizeof(prev_val_str) : sizeof(prev_val_dword);
bool del = false;
Win::RegKey hkcu;
Win::Reg::Key hkcu;
status = ::RegOpenCurrentUser(KEY_QUERY_VALUE | KEY_SET_VALUE, hkcu.ref());
check_reg_error<proxy_error>(status, "RegOpenCurrentUser");
@ -122,13 +122,13 @@ class WinProxySettings : public ProxySettings
str ? static_cast<DWORD>(strlen(prev_val_str) + 1) : sizeof(prev_val_dword));
}
void save_key(Win::RegKey &regkey, const std::string &key, const std::string &value, bool str)
void save_key(Win::Reg::Key &regkey, const std::string &key, const std::string &value, bool str)
{
LONG status;
char prev_val_str[1024] = {0}; // should be enought to fit proxy URL
DWORD prev_val_dword;
DWORD prev_buf_size = str ? sizeof(prev_val_str) : sizeof(prev_val_dword);
Win::RegKey hkcu;
Win::Reg::Key hkcu;
status = ::RegOpenCurrentUser(KEY_QUERY_VALUE | KEY_SET_VALUE, hkcu.ref());
check_reg_error<proxy_error>(status, "RegOpenCurrentUser");

View File

@ -4,7 +4,7 @@
// packet encryption, packet authentication, and
// packet compression.
//
// Copyright (C) 2012-2022 OpenVPN Inc.
// Copyright (C) 2012 OpenVPN Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License Version 3
@ -21,10 +21,10 @@
// registry utilities for Windows
#ifndef OPENVPN_WIN_REG_H
#define OPENVPN_WIN_REG_H
#pragma once
#include <windows.h>
#include <string>
#include <openvpn/win/winerr.hpp>
#include <openvpn/common/size.hpp>
@ -41,87 +41,374 @@ static void check_reg_error(DWORD status, const std::string &key)
}
}
// HKEY wrapper
class RegKey
/**
* @class Reg Abstraction of Windows Registry operations
*/
struct Reg
{
RegKey(const RegKey &) = delete;
RegKey &operator=(const RegKey &) = delete;
public:
RegKey() = default;
bool defined() const
/**
* @class Key Wrapper class for a Registry key handle
*/
class Key
{
return key != INVALID_HANDLE_VALUE;
}
PHKEY ref()
{
return &key;
}
HKEY operator()()
{
return key;
}
Key(const Key &) = delete;
Key &operator=(const Key &) = delete;
~RegKey()
{
if (defined())
::RegCloseKey(key);
}
private:
HKEY key = static_cast<HKEY>(INVALID_HANDLE_VALUE);
};
class RegKeyEnumerator : public std::vector<std::string>
{
public:
RegKeyEnumerator(HKEY hkey, const std::string &path)
{
RegKey regKey;
auto status = ::RegOpenKeyExA(hkey,
path.c_str(),
0,
KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS,
regKey.ref());
if (status != ERROR_SUCCESS)
return;
DWORD subkeys_num;
status = ::RegQueryInfoKeyA(regKey(),
nullptr,
nullptr,
NULL,
&subkeys_num,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr);
if (status != ERROR_SUCCESS)
return;
const int MAX_KEY_LENGTH = 255;
for (DWORD i = 0; i < subkeys_num; ++i)
public:
/**
* @brief Construct a Key with an open handle for a subkey under key
*
* In case the subkey cannot be opened or created, the handle remains invalid.
*
* @param key the key handle which the subkey is relative to
* @param subkey the subkey to open for the object
* @param create whether the subkey will be created if it doesn't exist
*/
Key(HKEY key, const std::wstring &subkey, bool create = false)
{
DWORD subkey_size = MAX_KEY_LENGTH;
char subkey[MAX_KEY_LENGTH];
status = ::RegEnumKeyExA(regKey(),
i,
subkey,
&subkey_size,
nullptr,
nullptr,
nullptr,
nullptr);
if (status == ERROR_SUCCESS)
push_back(subkey);
LSTATUS error;
if (create)
{
error = ::RegCreateKeyExW(key,
subkey.c_str(),
0,
NULL,
0,
KEY_ALL_ACCESS,
NULL,
&key_,
NULL);
}
else
{
error = ::RegOpenKeyExW(key,
subkey.c_str(),
0,
KEY_ALL_ACCESS,
&key_);
}
if (error)
{
key_ = static_cast<HKEY>(INVALID_HANDLE_VALUE);
}
}
Key(Key &key, const std::wstring &subkey, bool create = false)
: Key(key(), subkey, create)
{
}
/**
* @brief Construct a Key with an open handle for a subkey under HKLM
*
* In case the subkey cannot be opened or created, the handle remains invalid.
*
* @param subkey the subkey to open for the object
* @param create whether the subkey will be created if it doesn't exist
*/
Key(const std::wstring &subkey, bool create = false)
: Key(HKEY_LOCAL_MACHINE, subkey, create)
{
}
Key() = default;
Key(Key &&rhs)
{
if (defined())
{
::RegCloseKey(key_);
key_ = static_cast<HKEY>(INVALID_HANDLE_VALUE);
}
std::swap(key_, rhs.key_);
}
Key &operator=(Key &&rhs)
{
Key copy{std::move(rhs)};
std::swap(copy.key_, this->key_);
return *this;
}
~Key()
{
if (defined())
{
::RegCloseKey(key_);
}
}
/**
* @brief Check for a valid key handle
*
* @return true if the handle is valid
* @return false if the handle is invalid
*/
bool defined() const
{
return key_ != INVALID_HANDLE_VALUE;
}
/**
* @brief Retrun a pointer to the Registry key handle
*
* @return PHKEY the Registry key handle pointer
*/
PHKEY ref()
{
return &key_;
}
/**
* @brief Return the Registry key handle
* @return HKEY the key handle
*/
HKEY operator()()
{
return key_;
}
private:
HKEY key_ = static_cast<HKEY>(INVALID_HANDLE_VALUE);
};
/**
* @class KeyEnumerator Wrapper for Registry subkey enumeration
*
* The class is based on a std::vector and supports range based for
* loops. This makes it easy to enumerator over a range of subkeys.
*/
class KeyEnumerator : public std::vector<std::wstring>
{
public:
/**
* @brief Construct a new Key Enumerator object
*
* @param key The Registry key, its subkeys will be enumerated.
*/
KeyEnumerator(Key &key)
{
if (!key.defined())
return;
LSTATUS status;
DWORD subkeys_num;
status = ::RegQueryInfoKeyA(key(),
nullptr,
nullptr,
NULL,
&subkeys_num,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr);
if (status != ERROR_SUCCESS)
return;
constexpr int MAX_KEY_LENGTH = 255;
for (DWORD i = 0; i < subkeys_num; ++i)
{
DWORD subkey_size = MAX_KEY_LENGTH;
WCHAR subkey[MAX_KEY_LENGTH];
status = ::RegEnumKeyExW(key(),
i,
subkey,
&subkey_size,
nullptr,
nullptr,
nullptr,
nullptr);
if (status == ERROR_SUCCESS)
push_back(subkey);
}
}
};
/**
* Registry subkeys where IP configuration for interfaces can be found
*/
static constexpr WCHAR subkey_ipv4_itfs[] = LR"(SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces)";
static constexpr WCHAR subkey_ipv6_itfs[] = LR"(SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters\Interfaces)";
/**
* @brief Read a REG_DWORD value from the Windows registry
*
* @param key the key the value is to be read from
* @param name the name of the REG_DWORD value
* @return std::pair<std::wstring, LSTATUS> the string and the status code of
* the registry operations. If the string is empty, the status
* may indicate an error.
*/
static std::pair<DWORD, LSTATUS> get_dword(Key &key, PCWSTR name)
{
DWORD type;
DWORD value;
DWORD size = sizeof(value);
PBYTE data = reinterpret_cast<PBYTE>(&value);
LSTATUS err;
err = ::RegGetValueW(key(), NULL, name, RRF_RT_REG_DWORD, &type, data, &size);
if (err)
{
return {0, err};
}
else if (type != REG_DWORD)
{
return {0, ERROR_DATATYPE_MISMATCH};
}
return {value, err};
}
/**
* @brief Read a REG_SZ value from the Windows registry
*
* @param key the key the value is to be read from
* @param name the name of the REG_SZ value
* @return std::pair<std::wstring, LSTATUS> the string and the status code of
* the registry operations. If the string is empty, the status
* may indicate an error.
*/
static std::pair<std::wstring, LSTATUS> get_string(Key &key, PCWSTR name)
{
LSTATUS err;
DWORD size = 0;
DWORD type;
err = ::RegGetValueW(key(), NULL, name, RRF_RT_REG_SZ, &type, NULL, &size);
if (err)
{
return {{}, err};
}
else if (type != REG_SZ)
{
return {{}, ERROR_DATATYPE_MISMATCH};
}
std::wstring str(size / sizeof(std::wstring::value_type) + 1, '\0');
PBYTE data = reinterpret_cast<PBYTE>(str.data());
err = ::RegGetValueW(key(), NULL, name, RRF_RT_REG_SZ, NULL, data, &size);
if (err)
{
return {{}, err};
}
str.resize(::wcslen(str.c_str())); // remove trailing NULs
return {str, err};
}
/**
* @brief Read a REG_BINARY value from the Windows registry
*
* @param key the key the value is to be read from
* @param name the name of the REG_BINARY value
* @return std::pair<std::string, LSTATUS> the binary value and the status code of
* the registry operations. If the string is empty, the status
* may indicate an error. Otherwise string::data() and string::size()
* can be used to access the data from the Registry value.
*/
static std::pair<std::string, LSTATUS> get_binary(Key &key, PCWSTR name)
{
LSTATUS err;
DWORD size = 0;
DWORD type;
err = ::RegGetValueW(key(), NULL, name, RRF_RT_REG_BINARY, &type, NULL, &size);
if (err)
{
return {{}, err};
}
else if (type != REG_BINARY)
{
return {{}, ERROR_DATATYPE_MISMATCH};
}
std::string str(size, '\0');
PBYTE data = reinterpret_cast<PBYTE>(str.data());
err = ::RegGetValueW(key(), NULL, name, RRF_RT_REG_BINARY, NULL, data, &size);
if (err)
{
return {{}, err};
}
return {str, err};
}
/**
* @brief Set a DWORD value in the Registry
*
* @param key the Key where the value is set in
* @param name the name of the value
* @param value the value itself
* @return LSTATUS status of the Registry operation
*/
static LSTATUS set_dword(Key &key, PCWSTR name, DWORD value)
{
const BYTE *bytes = reinterpret_cast<const BYTE *>(&value);
DWORD size = static_cast<DWORD>(sizeof(value));
return ::RegSetValueExW(key(), name, 0, REG_DWORD, bytes, size);
}
/**
* @brief Set a REG_SZ value in the Registy
*
* @param key the Key where the value is set in
* @param name the name of the value
* @param value the value itself
* @return LSTATUS status of the Registry operation
*/
static LSTATUS set_string(Key &key, PCWSTR name, const std::wstring &value)
{
const BYTE *bytes = reinterpret_cast<const BYTE *>(value.c_str());
DWORD size = static_cast<DWORD>((value.size() + 1) * sizeof(std::wstring::value_type));
return ::RegSetValueExW(key(), name, 0, REG_SZ, bytes, size);
}
/**
* @brief Set a REG_MULTI_SZ value in the Registry
*
* Note the this function, unlike the one for REG_SZ values, expects the
* string value to be set to be a complete MULTI_SZ string, i.e. have two
* NUL characters at the end, and a NUL character between individual
* string subvalues.
*
* @param key the Key where the value is set in
* @param name the name of the value
* @param value the value itself
* @return LSTATUS status of the Registry operation
*/
static LSTATUS set_multi_string(Key &key, PCWSTR name, const std::wstring &value)
{
const BYTE *bytes = reinterpret_cast<const BYTE *>(value.data());
DWORD size = static_cast<DWORD>(value.size() * sizeof(std::wstring::value_type));
return ::RegSetValueExW(key(), name, 0, REG_MULTI_SZ, bytes, size);
}
/**
* @brief Delete a subkey from the Registry
*
* If the subkey contains values or subkeys, these are
* deleted from the Registry as well.
*
* @param subkey the subkey to be deleted
* @return LSTATUS status of the Registry operation
*/
static LSTATUS delete_subkey(const std::wstring &subkey)
{
return ::RegDeleteTreeW(HKEY_LOCAL_MACHINE, subkey.c_str());
}
/**
* @brief Delete a value from the Registry
*
* @param key the Key where the value is deleted from
* @param name the name of the value
* @return LSTATUS status of the Registry operation
*/
static LSTATUS delete_value(Key &key, PCWSTR name)
{
return ::RegDeleteValueW(key(), name);
}
};
} // namespace Win
} // namespace openvpn
#endif