/*
** 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 <https://www.gnu.org/licenses/>.
**/

#include "zbxsysinfo.h"
#include "../sysinfo.h"

#include "zbxwin32.h"
#include "zbxjson.h"
#include "zbxstr.h"

#define ZBX_REGVALUE_PRODUCTNAME	"ProductName"
#define ZBX_REGVALUE_BUILDLABEX		"BuildLabEx"
#define ZBX_REGVALUE_BUILDLAB		"BuildLab"
#define ZBX_REGVALUE_MAJOR		"CurrentBuildNumber"
#define ZBX_REGVALUE_MINOR		"UBR"
#define ZBX_REGVALUE_CSDVERSION		"CSDVersion"
#define ZBX_REGVALUE_CSDBUILDNUMBER	"CSDBuildNumber"
#define ZBX_REGVALUE_EDITION		"EditionID"
#define ZBX_REGVALUE_COMPOSITION	"CompositionEditionID"
#define ZBX_REGVALUE_DISPLAYVERSION	"DisplayVersion"
#define ZBX_REGVALUE_VERSION		"CurrentVersion"

#define ZBX_REGKEY_VERSION	"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"

/********************************************************************************
 *                                                                              *
 * Purpose: joins strings into one, placing separator in between them,          *
 *          skipping empty strings                                              *
 *                                                                              *
 * Parameters: separator - [IN] separator to place in between strings           *
 *             count     - [IN] number of strings                               *
 *             ...       - [IN] strings to join (strings can be empty  or NULL) *
 *                                                                              *
 ********************************************************************************/
static char	*join_nonempty_strs(const char *separator, size_t count, ...)
{
	char	*arg;
	va_list	args;
	char	**nonempty_strs;
	char	*res = NULL;
	size_t	num_nonempty = 0, str_size = 0;

	nonempty_strs = zbx_malloc(NULL, sizeof(char *) * count);

	va_start(args, count);

	for (size_t i = 0; i < count; i++)
	{
		arg = va_arg(args, char *);
		if (NULL != arg && 0 < strlen(arg))
		{
			str_size += strlen(arg) + strlen(separator);
			nonempty_strs[num_nonempty++] = arg;
		}
	}

	va_end(args);

	if (0 < num_nonempty)
	{
		res = zbx_malloc(NULL, str_size * sizeof(char));
		res[0] = '\0';
		strcat(res, nonempty_strs[0]);

		for (size_t i = 1; i < num_nonempty; i++) {
			strcat(res, separator);
			strcat(res, nonempty_strs[i]);
		}
	}
	zbx_free(nonempty_strs);

	return res;
}

static WORD	get_processor_architecture(void)
{
	typedef void (WINAPI *PGNSI)(LPSYSTEM_INFO);

	SYSTEM_INFO	si;
	PGNSI		pGNSI;

	memset(&si, 0, sizeof(si));

	if (NULL != (pGNSI = (PGNSI)GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "GetNativeSystemInfo")))
		pGNSI(&si);
	else
		GetSystemInfo(&si);

	return si.wProcessorArchitecture;
}

int	system_sw_arch(AGENT_REQUEST *request, AGENT_RESULT *result)
{
	typedef void (WINAPI *PGNSI)(LPSYSTEM_INFO);

	const char	*arch;

	switch (get_processor_architecture())
	{
		case PROCESSOR_ARCHITECTURE_INTEL:
			arch = "x86";
			break;
		case PROCESSOR_ARCHITECTURE_AMD64:
			arch = "x64";
			break;
		case PROCESSOR_ARCHITECTURE_IA64:
			arch = "Intel Itanium-based";
			break;
		default:
			SET_MSG_RESULT(result, zbx_strdup(NULL, "Unknown processor architecture."));
			return SYSINFO_RET_FAIL;
	}

	SET_STR_RESULT(result, zbx_strdup(NULL, arch));

	return SYSINFO_RET_OK;
}

static char	*get_registry_value(HKEY hKey, LPCTSTR name, DWORD value_type)
{
	DWORD	szData = 0;
	wchar_t	*value = NULL;
	char	*ret = NULL;

	if (ERROR_SUCCESS == RegQueryValueEx(hKey, name, NULL, NULL, NULL, &szData))
	{
		value = zbx_malloc(NULL, szData + sizeof(wchar_t));

		/* syscall RegQueryValueEx does not guarantee that the returned string will be '\0' terminated */
		if (ERROR_SUCCESS != RegQueryValueEx(hKey, name, NULL, NULL, (LPBYTE)value, &szData))
			zbx_free(value);
		else
			value[szData / sizeof(wchar_t)] = L'\0';
	}

	if (NULL == value)
		return NULL;

	if (REG_DWORD == value_type)
		ret = zbx_dsprintf(NULL, "%d", (uint32_t)*(DWORD *)value);
	else
		ret = zbx_unicode_to_utf8(value);

	zbx_free(value);

	return ret;
}

static HKEY	open_registry_info_key(void)
{
	HKEY	handle = NULL;

	if (ERROR_SUCCESS != RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT(ZBX_REGKEY_VERSION), 0, KEY_READ, &handle))
	{
		zabbix_log(LOG_LEVEL_DEBUG, "failed to open registry key '%s'", ZBX_REGKEY_VERSION);
		return NULL;
	}

	return handle;
}

static char	*get_build_string(HKEY handle, int include_descriptor)
{
	char	*major = NULL, *minor = NULL, *str = NULL;

	if (NULL != (major = get_registry_value(handle, TEXT(ZBX_REGVALUE_MAJOR), REG_MULTI_SZ)))
		minor = get_registry_value(handle, TEXT(ZBX_REGVALUE_MINOR), REG_DWORD);

	str = join_nonempty_strs(".", 2, major, minor);
	zbx_free(major);
	zbx_free(minor);

	if (include_descriptor && 0 < strlen(str))
		return zbx_dsprintf(str, "Build %s", str);

	return str;
}

static char	*get_full_os_info(HKEY handle)
{
	char	*name, *lab, *build, *res = NULL;

	name = get_registry_value(handle, TEXT(ZBX_REGVALUE_PRODUCTNAME), REG_MULTI_SZ);
	lab = get_registry_value(handle, TEXT(ZBX_REGVALUE_BUILDLABEX), REG_MULTI_SZ);

	if (NULL == lab)
		lab = get_registry_value(handle, TEXT(ZBX_REGVALUE_BUILDLAB), REG_MULTI_SZ);
	build = get_build_string(handle, 1);

	res = join_nonempty_strs(" ", 3, name, lab, build);
	zbx_free(name);
	zbx_free(lab);
	zbx_free(build);

	return res;
}

static char	*get_pretty_os_info(HKEY handle)
{
	char	*name, *build, *sp_version, *res = NULL;

	name = get_registry_value(handle, TEXT(ZBX_REGVALUE_PRODUCTNAME), REG_MULTI_SZ);
	build = get_build_string(handle, 1);
	sp_version = get_registry_value(handle, TEXT(ZBX_REGVALUE_CSDVERSION), REG_MULTI_SZ);

	res = join_nonempty_strs(" ", 3, name, build, sp_version);
	zbx_free(name);
	zbx_free(build);
	zbx_free(sp_version);

	return res;
}

int	system_sw_os(AGENT_REQUEST *request, AGENT_RESULT *result)
{
	char	*type, *str;
	int	ret = SYSINFO_RET_FAIL;
	HKEY	handle = NULL;

	if (1 < request->nparam)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters."));
		return ret;
	}

	if (NULL == (handle = open_registry_info_key()))
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Could not open registry key " ));
		goto out;
	}

	type = get_rparam(request, 0);

	if (NULL == type || '\0' == *type || 0 == strcmp(type, "full"))
	{
		if (NULL != (str = get_full_os_info(handle)))
			ret = SYSINFO_RET_OK;
	}
	else if (0 == strcmp(type, "short"))
	{
		if (NULL != (str = get_pretty_os_info(handle)))
			ret = SYSINFO_RET_OK;
	}
	else if (0 == strcmp(type, "name"))
	{
		if (NULL != (str = get_registry_value(handle, TEXT(ZBX_REGVALUE_PRODUCTNAME), REG_MULTI_SZ)))
			ret = SYSINFO_RET_OK;
	}
	else
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Invalid first parameter."));
		return ret;
	}

	if (SYSINFO_RET_OK == ret)
	{
		SET_STR_RESULT(result, zbx_strdup(NULL, str));
		zbx_free(str);
	}
	else
	{
		/* if we were not able to get any data, no values could be retrieved */
		/* in error specify that ProductName is missing because it is required in all cases */
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Could not read registry value " ZBX_REGVALUE_PRODUCTNAME));
	}
out:
	RegCloseKey(handle);

	return ret;
}

int	system_sw_os_get(AGENT_REQUEST *request, AGENT_RESULT *result)
{
#define SW_OS_GET_TYPE		"os_type"
#define SW_OS_GET_PROD_NAME	"product_name"
#define SW_OS_GET_ARCH		"architecture"
#define SW_OS_GET_BLD_MAJOR	"build_number"
#define SW_OS_GET_BLD_MINOR	"build_revision"
#define SW_OS_GET_BLD		"build"
#define SW_OS_GET_EDITION	"edition"
#define SW_OS_GET_COMPOSITION	"composition"
#define SW_OS_GET_DSPL_VER	"display_version"
#define SW_OS_GET_SP_VER	"sp_version"
#define SW_OS_GET_SP_BUILD	"sp_build"
#define SW_OS_GET_VER		"version"
#define SW_OS_GET_VER_PRETTY	"version_pretty"
#define SW_OS_GET_VER_FULL	"version_full"

	struct zbx_json	j;
	char		*str;
	const char	*arch;
	HKEY		handle = NULL;

	ZBX_UNUSED(request);

	if (0 < request->nparam)
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "Too many parameters."));
		return SYSINFO_RET_FAIL;
	}

	zbx_json_init(&j, ZBX_JSON_STAT_BUF_LEN);
	zbx_json_addstring(&j, SW_OS_GET_TYPE, "windows", ZBX_JSON_TYPE_STRING);

	if (NULL == (handle = open_registry_info_key()))
		goto out;

	if (NULL != (str = get_registry_value(handle, TEXT(ZBX_REGVALUE_PRODUCTNAME), REG_MULTI_SZ)))
	{
		zbx_json_addstring(&j, SW_OS_GET_PROD_NAME, str, ZBX_JSON_TYPE_STRING);
		zbx_free(str);
	}

	switch(get_processor_architecture())
	{
		case PROCESSOR_ARCHITECTURE_AMD64:
			arch = "x86_64";
			break;
		case PROCESSOR_ARCHITECTURE_INTEL:
			arch = "x86";
			break;
		case PROCESSOR_ARCHITECTURE_ARM:
			arch = "arm";
			break;
		case PROCESSOR_ARCHITECTURE_ARM64:
			arch = "arm64";
			break;
		case PROCESSOR_ARCHITECTURE_IA64:
			arch = "Intel Itanium";
			break;
		default:
			arch = "unknown";
	}
	zbx_json_addstring(&j, SW_OS_GET_ARCH, arch, ZBX_JSON_TYPE_STRING);

	if (NULL != (str = get_registry_value(handle, TEXT(ZBX_REGVALUE_MAJOR), REG_MULTI_SZ)))
	{
		zbx_json_addstring(&j, SW_OS_GET_BLD_MAJOR, str, ZBX_JSON_TYPE_STRING);
		zbx_free(str);
	}

	if (NULL != (str = get_registry_value(handle, TEXT(ZBX_REGVALUE_MINOR), REG_DWORD)))
	{
		zbx_json_addstring(&j, SW_OS_GET_BLD_MINOR, str, ZBX_JSON_TYPE_STRING);
		zbx_free(str);
	}

	if (NULL != (str = get_build_string(handle, 0)))
	{
		zbx_json_addstring(&j, SW_OS_GET_BLD, str, ZBX_JSON_TYPE_STRING);
		zbx_free(str);
	}

	if (NULL != (str = get_registry_value(handle, TEXT(ZBX_REGVALUE_EDITION), REG_MULTI_SZ)))
	{
		zbx_json_addstring(&j, SW_OS_GET_EDITION, str, ZBX_JSON_TYPE_STRING);
		zbx_free(str);
	}

	if (NULL != (str = get_registry_value(handle, TEXT(ZBX_REGVALUE_COMPOSITION), REG_MULTI_SZ)))
	{
		zbx_json_addstring(&j, SW_OS_GET_COMPOSITION, str, ZBX_JSON_TYPE_STRING);
		zbx_free(str);
	}

	if (NULL != (str = get_registry_value(handle, TEXT(ZBX_REGVALUE_DISPLAYVERSION), REG_MULTI_SZ)))
	{
		zbx_json_addstring(&j, SW_OS_GET_DSPL_VER, str, ZBX_JSON_TYPE_STRING);
		zbx_free(str);
	}

	if (NULL != (str = get_registry_value(handle, TEXT(ZBX_REGVALUE_CSDVERSION), REG_MULTI_SZ)))
	{
		zbx_json_addstring(&j, SW_OS_GET_SP_VER, str, ZBX_JSON_TYPE_STRING);
		zbx_free(str);
	}

	if (NULL != (str = get_registry_value(handle, TEXT(ZBX_REGVALUE_CSDBUILDNUMBER), REG_MULTI_SZ)))
	{
		zbx_json_addstring(&j, SW_OS_GET_SP_BUILD, str, ZBX_JSON_TYPE_STRING);
		zbx_free(str);
	}

	if (NULL != (str = get_registry_value(handle, TEXT(ZBX_REGVALUE_VERSION), REG_MULTI_SZ)))
	{
		zbx_json_addstring(&j, SW_OS_GET_VER, str, ZBX_JSON_TYPE_STRING);
		zbx_free(str);
	}

	if (NULL != (str = get_pretty_os_info(handle)))
	{
		zbx_json_addstring(&j, SW_OS_GET_VER_PRETTY, str, ZBX_JSON_TYPE_STRING);
		zbx_free(str);
	}

out:
	if (NULL != handle && NULL != (str = get_full_os_info(handle)))
	{
		zbx_json_addstring(&j, SW_OS_GET_VER_FULL, str, ZBX_JSON_TYPE_STRING);
		zbx_free(str);
	}
	else
		zbx_json_addstring(&j, SW_OS_GET_VER_FULL, "", ZBX_JSON_TYPE_STRING);

	RegCloseKey(handle);
	SET_STR_RESULT(result, strdup(j.buffer));
	zbx_json_free(&j);

	return SYSINFO_RET_OK;

#undef SW_OS_GET_TYPE
#undef SW_OS_GET_PROD_NAME
#undef SW_OS_GET_ARCH
#undef SW_OS_GET_BLD_MAJOR
#undef SW_OS_GET_BLD_MINOR
#undef SW_OS_GET_BLD
#undef SW_OS_GET_EDITION
#undef SW_OS_GET_COMPOSITION
#undef SW_OS_GET_DSPL_VER
#undef SW_OS_GET_SP_VER
#undef SW_OS_GET_SP_BUILD
#undef SW_OS_GET_VER
#undef SW_OS_GET_VER_PRETTY
#undef SW_OS_GET_VER_FULL
}