/*
** Copyright (C) 2001-2025 Zabbix SIA
**
** This program is free software: you can redistribute it and/or modify it under the terms of
** the GNU Affero General Public License as published by the Free Software Foundation, version 3.
**
** This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
** without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
** See the GNU Affero General Public License for more details.
**
** You should have received a copy of the GNU Affero General Public License along with this program.
** If not, see .
**/
#include "zbxsysinfo.h"
#include "../sysinfo.h"
#include "zbxstr.h"
#include "zbxcrypto.h"
#include "zbxjson.h"
#include "zbxalgo.h"
#include "zbxregexp.h"
#include "zbxlog.h"
#include
#include
#define REGISTRY_DISCOVERY_MODE_KEYS 0
#define REGISTRY_DISCOVERY_MODE_VALUES 1
static HKEY get_hkey_from_fullkey(char *fullkey)
{
if (0 == strcmp("HKEY_CLASSES_ROOT", fullkey) || 0 == strcmp("HKCR", fullkey))
return HKEY_CLASSES_ROOT;
else if (0 == strcmp("HKEY_CURRENT_CONFIG", fullkey) || 0 == strcmp("HKCC", fullkey))
return HKEY_CURRENT_CONFIG;
else if (0 == strcmp("HKEY_CURRENT_USER", fullkey) || 0 == strcmp("HKCU", fullkey))
return HKEY_CURRENT_USER;
else if (0 == strcmp("HKEY_CURRENT_USER_LOCAL_SETTINGS", fullkey) || 0 == strcmp("HKCULS", fullkey))
return HKEY_CURRENT_USER_LOCAL_SETTINGS;
else if (0 == strcmp("HKEY_LOCAL_MACHINE", fullkey) || 0 == strcmp("HKLM", fullkey))
return HKEY_LOCAL_MACHINE;
else if (0 == strcmp("HKEY_PERFORMANCE_DATA", fullkey) || 0 == strcmp("HKPD", fullkey))
return HKEY_PERFORMANCE_DATA;
else if (0 == strcmp("HKEY_PERFORMANCE_NLSTEXT", fullkey) || 0 == strcmp("HKPN", fullkey))
return HKEY_PERFORMANCE_NLSTEXT;
else if (0 == strcmp("HKEY_PERFORMANCE_TEXT", fullkey) || 0 == strcmp("HKPT", fullkey))
return HKEY_PERFORMANCE_TEXT;
else if (0 == strcmp("HKEY_USERS", fullkey) || 0 == strcmp("HKU", fullkey))
return HKEY_USERS;
return 0;
}
static const char *registry_type_to_string(DWORD type)
{
switch (type)
{
case REG_BINARY:
return "REG_BINARY";
case REG_DWORD:
return "REG_DWORD";
case REG_EXPAND_SZ:
return "REG_EXPAND_SZ";
case REG_LINK:
return "REG_LINK";
case REG_MULTI_SZ:
return "REG_MULTI_SZ";
case REG_NONE:
return "REG_NONE";
case REG_QWORD:
return "REG_QWORD";
case REG_SZ:
return "REG_SZ";
}
return "Unknown";
}
static void registry_get_multistring_value(const wchar_t *wbuffer, struct zbx_json *j)
{
while (L'\0' != *wbuffer)
{
char *buffer = zbx_unicode_to_utf8(wbuffer);
zbx_json_addstring(j, NULL, buffer, ZBX_JSON_TYPE_STRING);
zbx_free(buffer);
wbuffer += wcslen(wbuffer) + 1 ;
}
}
static int convert_value(DWORD type, const char *value, DWORD value_len, char **out)
{
struct zbx_json j;
switch (type) {
case REG_BINARY:
zbx_base64_encode_dyn(value, out, (int)value_len);
return SUCCEED;
case REG_DWORD:
*out = zbx_dsprintf(NULL, "%u", *(DWORD *)value);
return SUCCEED;
case REG_QWORD:
*out = zbx_dsprintf(NULL, "%lu", *(DWORDLONG *)value);
return SUCCEED;
case REG_MULTI_SZ:
zbx_json_init(&j, ZBX_JSON_STAT_BUF_LEN);
zbx_json_initarray(&j, ZBX_JSON_STAT_BUF_LEN);
registry_get_multistring_value((wchar_t *)value, &j);
zbx_json_close(&j);
*out = zbx_strdup(NULL, j.buffer);
zbx_json_free(&j);
return SUCCEED;
case REG_NONE:
*out = NULL;
return SUCCEED;
case REG_SZ:
case REG_EXPAND_SZ:
*out = zbx_unicode_to_utf8((wchar_t *)value);
return SUCCEED;
default:
return FAIL;
}
}
ZBX_PTR_VECTOR_DECL(wchar_ptr, wchar_t *)
ZBX_PTR_VECTOR_IMPL(wchar_ptr, wchar_t *)
static void discovery_get_regkey_values(HKEY hKey, wchar_t *current_subkey, struct zbx_json *j, int mode,
wchar_t *root, const char *regexp)
{
#define ZBX_SYSINFO_REGISTRY_TAG_FULLKEY "fullkey"
#define ZBX_SYSINFO_REGISTRY_TAG_LASTKEY "lastsubkey"
#define ZBX_SYSINFO_REGISTRY_TAG_NAME "name"
#define ZBX_SYSINFO_REGISTRY_TAG_DATA "data"
#define ZBX_SYSINFO_REGISTRY_TAG_TYPE "type"
#define MAX_VALUE_NAME 16383
wchar_t achClass[MAX_PATH] = TEXT(""), achValue[MAX_VALUE_NAME];
DWORD cchClassName = MAX_PATH, cSubKeys=0, cValues, cbName, i, retCode,
cchValue = MAX_VALUE_NAME;
char *uroot, *usubkey;
zbx_vector_wchar_ptr_t wsubkeys;
retCode = RegQueryInfoKey(hKey, achClass, &cchClassName, NULL, &cSubKeys, NULL, NULL, &cValues, NULL, NULL,
NULL, NULL);
zbx_vector_wchar_ptr_create(&wsubkeys);
uroot = zbx_unicode_to_utf8(root);
usubkey = zbx_unicode_to_utf8(current_subkey);
if (REGISTRY_DISCOVERY_MODE_KEYS == mode)
{
if (*usubkey != '\0')
{
zbx_json_addobject(j, NULL);
zbx_json_addstring(j, ZBX_SYSINFO_REGISTRY_TAG_FULLKEY, uroot, ZBX_JSON_TYPE_STRING);
zbx_json_addstring(j, ZBX_SYSINFO_REGISTRY_TAG_LASTKEY, usubkey, ZBX_JSON_TYPE_STRING);
zbx_json_close(j);
}
zbx_free(uroot);
zbx_free(usubkey);
}
for (i = 0; i < cSubKeys; i++)
{
#define MAX_KEY_LENGTH 255
cbName = MAX_KEY_LENGTH;
#undef MAX_KEY_LENGTH
retCode = RegEnumKeyEx(hKey, i, achClass, &cbName, NULL, NULL, NULL, NULL);
if (ERROR_SUCCESS == retCode)
zbx_vector_wchar_ptr_append(&wsubkeys, wcsdup(achClass));
}
for (i = 0; i < (DWORD)wsubkeys.values_num; i++)
{
#define MAX_FULLKEY_LENGTH 4096
HKEY hSubkey;
wchar_t wnew_root[MAX_FULLKEY_LENGTH];
wchar_t *wsubkey;
wsubkey = wsubkeys.values[i];
if (0 == wcscmp(wsubkey, L""))
continue;
_snwprintf_s(wnew_root, MAX_FULLKEY_LENGTH, MAX_FULLKEY_LENGTH, L"%s\\%s", root, wsubkey);
if (ERROR_SUCCESS == RegOpenKeyEx(hKey, wsubkey, 0, KEY_READ, &hSubkey))
discovery_get_regkey_values(hSubkey, wsubkey, j, mode, wnew_root, regexp);
RegCloseKey(hSubkey);
#undef MAX_FULLKEY_LENGTH
}
zbx_vector_wchar_ptr_clear_ext(&wsubkeys, zbx_ptr_free);
zbx_vector_wchar_ptr_destroy(&wsubkeys);
if (REGISTRY_DISCOVERY_MODE_VALUES == mode && 0 != cValues)
{
DWORD buffer_alloc = 1024;
char *buffer;
buffer = zbx_malloc(NULL, buffer_alloc);
for (i = 0, retCode = ERROR_SUCCESS; i < cValues; i++)
{
DWORD valueType, value_len = buffer_alloc;
char *uvaluename, *out = NULL;
cchValue = MAX_VALUE_NAME;
achValue[0] = L'\0';
if (ERROR_MORE_DATA == (retCode = RegEnumValue(hKey, i, achValue, &cchValue, NULL, &valueType,
buffer, &value_len)))
{
buffer = zbx_realloc(buffer, value_len);
buffer_alloc = value_len;
cchValue = MAX_VALUE_NAME;
retCode = RegEnumValue(hKey, i, achValue, &cchValue, NULL, &valueType, buffer,
&value_len);
}
if (ERROR_SUCCESS != retCode)
continue;
uvaluename = zbx_unicode_to_utf8(achValue);
if (NULL != regexp && '\0' != *regexp)
{
if (NULL == zbx_regexp_match(uvaluename, regexp, NULL))
{
zbx_free(uvaluename);
continue;
}
}
if (SUCCEED != convert_value(valueType, buffer, value_len, &out))
continue;
zbx_json_addobject(j, NULL);
zbx_json_addstring(j, ZBX_SYSINFO_REGISTRY_TAG_FULLKEY, uroot,
ZBX_JSON_TYPE_STRING);
zbx_json_addstring(j, ZBX_SYSINFO_REGISTRY_TAG_LASTKEY, usubkey,
ZBX_JSON_TYPE_STRING);
zbx_json_addstring(j, ZBX_SYSINFO_REGISTRY_TAG_NAME, uvaluename, ZBX_JSON_TYPE_STRING);
zbx_free(uvaluename);
switch (valueType)
{
case REG_DWORD:
case REG_QWORD:
zbx_json_addstring(j, ZBX_SYSINFO_REGISTRY_TAG_DATA, out, ZBX_JSON_TYPE_INT);
break;
case REG_NONE:
zbx_json_adduint64(j, ZBX_SYSINFO_REGISTRY_TAG_DATA, 0);
break;
case REG_MULTI_SZ:
zbx_json_addraw(j, ZBX_SYSINFO_REGISTRY_TAG_DATA, out);
break;
default:
zbx_json_addstring(j, ZBX_SYSINFO_REGISTRY_TAG_DATA, out, ZBX_JSON_TYPE_STRING);
}
zbx_free(out);
zbx_json_addstring(j, ZBX_SYSINFO_REGISTRY_TAG_TYPE,
registry_type_to_string(valueType), ZBX_JSON_TYPE_STRING);
zbx_json_close(j);
}
zbx_free(buffer);
}
zbx_free(uroot);
zbx_free(usubkey);
#undef ZBX_SYSINFO_REGISTRY_TAG_FULLKEY
#undef ZBX_SYSINFO_REGISTRY_TAG_LASTKEY
#undef ZBX_SYSINFO_REGISTRY_TAG_NAME
#undef ZBX_SYSINFO_REGISTRY_TAG_DATA
#undef ZBX_SYSINFO_REGISTRY_TAG_TYPE
#undef MAX_VALUE_NAME
}
static int split_fullkey(char **fullkey, HKEY *hive_handle, char **hive_str)
{
char *end;
if (NULL == (end = strchr(*fullkey, '\\')))
return FAIL;
*end = '\0';
if (NULL == (*hive_handle = get_hkey_from_fullkey(*fullkey)))
return FAIL;
if (NULL != hive_str)
*hive_str = *fullkey;
*fullkey = *fullkey + (end - *fullkey) + 1;
return SUCCEED;
}
static int registry_discover(char *key, int mode, AGENT_RESULT *result, const char *regexp)
{
wchar_t *wkey, *wfullkey = NULL;
HKEY hkey, hive_handle;
struct zbx_json j;
DWORD retCode;
int ret = SUCCEED;
char *hive_str, *fullkey = NULL;
if (FAIL == split_fullkey(&key, &hive_handle, &hive_str))
{
SET_MSG_RESULT(result, zbx_strdup(NULL, "Failed to parse registry key."));
return FAIL;
}
zbx_json_initarray(&j, ZBX_JSON_STAT_BUF_LEN);
wkey = zbx_utf8_to_unicode(key);
if (ERROR_SUCCESS == (retCode = RegOpenKeyEx(hive_handle, wkey, 0, KEY_READ, &hkey)))
{
fullkey = zbx_dsprintf(fullkey, "%s\\%s", hive_str, key);
wfullkey = zbx_utf8_to_unicode(fullkey);
discovery_get_regkey_values(hkey, L"", &j, mode, wfullkey, regexp);
}
else
{
SET_MSG_RESULT(result, zbx_strdup(NULL, zbx_strerror_from_system(retCode)));
ret = FAIL;
goto out;
}
RegCloseKey(hkey);
zbx_json_close(&j);
SET_STR_RESULT(result, zbx_strdup(NULL, j.buffer));
out:
zbx_json_free(&j);
zbx_free(wkey);
zbx_free(fullkey);
zbx_free(wfullkey);
return ret;
}
static int registry_get_value(char *key, const char *value, AGENT_RESULT *result)
{
wchar_t *wkey, *wvalue;
char *data = NULL, *bin_value = NULL, *value_str = NULL;
DWORD BufferSize = 0, type;
LSTATUS errCode;
HKEY hive_handle;
int ret = SUCCEED;
if (FAIL == split_fullkey(&key, &hive_handle, NULL))
{
SET_MSG_RESULT(result, zbx_strdup(NULL, "Failed to parse registry key."));
return FAIL;
}
wkey = zbx_utf8_to_unicode(key);
wvalue = (NULL != value ? zbx_utf8_to_unicode(value) : NULL);
errCode = RegGetValue(hive_handle, wkey, wvalue, RRF_RT_ANY, &type, NULL, &BufferSize);
if (ERROR_SUCCESS == errCode)
{
data = zbx_malloc(NULL, (size_t)BufferSize);
errCode = RegGetValue(hive_handle, wkey, wvalue, RRF_RT_ANY, &type, (PVOID)data, &BufferSize);
}
if (ERROR_SUCCESS != errCode)
{
SET_MSG_RESULT(result, zbx_strdup(NULL, zbx_strerror_from_system(errCode)));
ret = FAIL;
goto out;
}
if (SUCCEED == (ret = convert_value(type, data, BufferSize, &value_str)))
SET_STR_RESULT(result, value_str);
else
SET_MSG_RESULT(result, zbx_strdup(NULL, "Unsupported registry data type."));
out:
zbx_free(wkey);
zbx_free(wvalue);
zbx_free(data);
return ret;
}
int registry_data(AGENT_REQUEST *request, AGENT_RESULT *result)
{
char *regkey, *value_name;
if (2 < request->nparam)
{
SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters."));
return SYSINFO_RET_FAIL;
}
regkey = get_rparam(request, 0);
if (NULL == regkey || '\0' == *regkey)
{
SET_MSG_RESULT(result, zbx_strdup(NULL, "Registry key is not supplied."));
return SYSINFO_RET_FAIL;
}
value_name = get_rparam(request, 1);
if (FAIL == registry_get_value(regkey, value_name, result))
return SYSINFO_RET_FAIL;
return SYSINFO_RET_OK;
}
int registry_get(AGENT_REQUEST *request, AGENT_RESULT *result)
{
char *pkey, *pmode, *regexp;
int mode;
if (3 < request->nparam)
{
SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters."));
return SYSINFO_RET_FAIL;
}
pkey = get_rparam(request, 0);
if (NULL == pkey || '\0' == *pkey)
{
SET_MSG_RESULT(result, zbx_strdup(NULL, "Registry key is not supplied."));
return SYSINFO_RET_FAIL;
}
pmode = get_rparam(request, 1);
if (NULL == pmode || '\0' == *pmode || 0 == strcmp(pmode, "values"))
{
mode = REGISTRY_DISCOVERY_MODE_VALUES;
}
else if (0 == strcmp(pmode, "keys"))
{
mode = REGISTRY_DISCOVERY_MODE_KEYS;
if (2 < request->nparam)
{
SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters."));
return SYSINFO_RET_FAIL;
}
}
else
{
SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid 'mode' parameter."));
return SYSINFO_RET_FAIL;
}
regexp = get_rparam(request, 2);
if (FAIL == registry_discover(pkey, mode, result, regexp))
return SYSINFO_RET_FAIL;
return SYSINFO_RET_OK;
}