/*
** 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 "zbxcfg.h"
#include "zbxtime.h"

#include "zbxwin32.h"

#pragma comment(lib, "user32.lib")

/******************************************************************************
 *                                                                            *
 * Purpose: reads value from Windows registry                                 *
 *                                                                            *
 ******************************************************************************/
static wchar_t	*read_registry_value(HKEY hKey, LPCTSTR name)
{
	DWORD	szData = 0;
	wchar_t	*value = NULL;

	if (ERROR_SUCCESS == RegQueryValueEx(hKey, name, NULL, NULL, NULL, &szData))
	{
		value = zbx_malloc(NULL, szData);
		if (ERROR_SUCCESS != RegQueryValueEx(hKey, name, NULL, NULL, (LPBYTE)value, &szData))
			zbx_free(value);
	}

	return value;
}

/******************************************************************************
 *                                                                            *
 * Purpose: gets Windows version information                                  *
 *                                                                            *
 ******************************************************************************/
const OSVERSIONINFOEX		*zbx_win_getversion(void)
{
#define ZBX_REGKEY_VERSION		"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"
#define ZBX_REGVALUE_CURRENTVERSION	"CurrentVersion"
#define ZBX_REGVALUE_CURRENTBUILDNUMBER	"CurrentBuildNumber"
#define ZBX_REGVALUE_CSDVERSION		"CSDVersion"
#define ZBX_REGKEY_PRODUCT		"System\\CurrentControlSet\\Control\\ProductOptions"
#define ZBX_REGVALUE_PRODUCTTYPE		"ProductType"

	static OSVERSIONINFOEX	vi = {sizeof(OSVERSIONINFOEX)};

	OSVERSIONINFOEX		*pvi = NULL;
	HKEY			h_key_registry = NULL;
	wchar_t			*key_value = NULL, *ptr;

	if (0 != vi.dwMajorVersion)
		return &vi;

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

	if (NULL == (key_value = read_registry_value(h_key_registry, TEXT(ZBX_REGVALUE_CURRENTVERSION))))
	{
		zabbix_log(LOG_LEVEL_DEBUG, "failed to read registry value '%s'", ZBX_REGVALUE_CURRENTVERSION);
		goto out;
	}

	if (NULL != (ptr = wcschr(key_value, TEXT('.'))))
	{
		*ptr++ = L'\0';
		vi.dwMinorVersion = _wtoi(ptr);
	}

	vi.dwMajorVersion = _wtoi(key_value);

	zbx_free(key_value);

	if (6 > vi.dwMajorVersion || 2 > vi.dwMinorVersion)
	{
		GetVersionEx((OSVERSIONINFO *)&vi);
	}
	else
	{
		if (NULL != (key_value = read_registry_value(h_key_registry, TEXT(ZBX_REGVALUE_CSDVERSION))))
		{
			wcscpy_s(vi.szCSDVersion, sizeof(vi.szCSDVersion) / sizeof(*vi.szCSDVersion), key_value);

			zbx_free(key_value);
		}

		if (NULL == (key_value = read_registry_value(h_key_registry, TEXT(ZBX_REGVALUE_CURRENTBUILDNUMBER))))
		{
			zabbix_log(LOG_LEVEL_DEBUG, "failed to read registry value '%s'",
					ZBX_REGVALUE_CURRENTBUILDNUMBER);
			goto out;
		}

		vi.dwBuildNumber = _wtoi(key_value);
		zbx_free(key_value);

		RegCloseKey(h_key_registry);
		h_key_registry = NULL;

		if (ERROR_SUCCESS != RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT(ZBX_REGKEY_PRODUCT), 0, KEY_READ,
				&h_key_registry))
		{
			zabbix_log(LOG_LEVEL_DEBUG, "failed to open registry key '%s'", ZBX_REGKEY_PRODUCT);
			goto out;
		}

		if (NULL == (key_value = read_registry_value(h_key_registry, TEXT(ZBX_REGVALUE_PRODUCTTYPE))))
		{
			zabbix_log(LOG_LEVEL_DEBUG, "failed to read registry value '%s'", ZBX_REGVALUE_PRODUCTTYPE);
			goto out;
		}

		if (0 == wcscmp(key_value, L"WinNT"))
			vi.wProductType = 1;
		else if (0 == wcscmp(key_value, L"LenmanNT"))
			vi.wProductType = 2;
		else if (0 == wcscmp(key_value, L"ServerNT"))
			vi.wProductType = 3;

		zbx_free(key_value);

		vi.dwPlatformId = VER_PLATFORM_WIN32_NT;
	}

	pvi = &vi;
out:
	if (NULL != h_key_registry)
		RegCloseKey(h_key_registry);

	return pvi;
#undef ZBX_REGKEY_VERSION
#undef ZBX_REGVALUE_CURRENTVERSION
#undef ZBX_REGVALUE_CURRENTBUILDNUMBER
#undef ZBX_REGVALUE_CSDVERSION
#undef ZBX_REGKEY_PRODUCT
#undef ZBX_REGVALUE_PRODUCTTYPE
}

static void	get_wmi_check_timeout(const char *wmi_namespace, const char *query, char **var,
		double time_first_query_started, double *time_previous_query_finished)
{
	double	time_left = sysinfo_get_config_timeout() - (*time_previous_query_finished -
			time_first_query_started);

	if (0 >= time_left)
		return;

	zbx_wmi_get(wmi_namespace, query, time_left, var);
	*time_previous_query_finished = zbx_time();
}

int	system_uname(AGENT_REQUEST *request, AGENT_RESULT *result)
{
	char	*os = NULL;
	size_t	os_alloc = 0, os_offset = 0;
	char	*sysname = "Windows", *os_csname = NULL, *os_version = NULL, *os_caption = NULL, *os_csdversion = NULL,
		*proc_architecture = NULL, *proc_addresswidth = NULL, *wmi_namespace = "root\\cimv2",
		*arch = "<unknown architecture>";
	int	ret = SYSINFO_RET_FAIL;
	double	start_time = zbx_time();
	double	time_previous_query_finished = start_time;

	/* Emulates uname(2) (POSIX) since it is not provided natively by Windows by taking */
	/* the relevant values from Win32_OperatingSystem and Win32_Processor WMI classes.  */
	/* It was decided that in context of Windows OS ISA is more useful information than */
	/* CPU architecture. This is contrary to POSIX and uname(2) in Unix.                */

	get_wmi_check_timeout(wmi_namespace, "select CSName from Win32_OperatingSystem", &os_csname, start_time,
			&time_previous_query_finished);
	get_wmi_check_timeout(wmi_namespace, "select Version from Win32_OperatingSystem", &os_version, start_time,
			&time_previous_query_finished);
	get_wmi_check_timeout(wmi_namespace, "select Caption from Win32_OperatingSystem", &os_caption, start_time,
			&time_previous_query_finished);
	get_wmi_check_timeout(wmi_namespace, "select CSDVersion from Win32_OperatingSystem", &os_csdversion, start_time,
			&time_previous_query_finished);
	get_wmi_check_timeout(wmi_namespace, "select Architecture from Win32_Processor", &proc_architecture, start_time,
			&time_previous_query_finished);
	get_wmi_check_timeout(wmi_namespace, "select AddressWidth from Win32_Processor", &proc_addresswidth, start_time,
			&time_previous_query_finished);

	if (0 >= sysinfo_get_config_timeout() - (time_previous_query_finished - start_time))
	{
		SET_MSG_RESULT(result, zbx_strdup(NULL, "WMI aggregate query timeout"));
	}
	else
	{
		if (NULL != proc_architecture)
		{
			switch (atoi(proc_architecture))
			{
				case 0: arch = "x86"; break;
				case 6: arch = "ia64"; break;
				case 9:
					if (NULL != proc_addresswidth)
					{
						if (32 == atoi(proc_addresswidth))
							arch = "x86";
						else
							arch = "x64";
					}

					break;
			}
		}

		/* The comments indicate the relevant field in struct utsname (POSIX) that is used in uname(2). */
		zbx_snprintf_alloc(&os, &os_alloc, &os_offset, "%s %s %s %s%s%s %s",
				sysname,						/* sysname */
				os_csname ? os_csname : "<unknown nodename>",		/* nodename */
				os_version ? os_version : "<unknown release>",		/* release */
				os_caption ? os_caption : "<unknown version>",		/* version */
				os_caption && os_csdversion ? " " : "",
				os_caption && os_csdversion ? os_csdversion : "",	/* version (cont.) */
				arch);							/* machine */

		SET_STR_RESULT(result, os);

		ret = SYSINFO_RET_OK;
	}

	zbx_free(os_csname);
	zbx_free(os_version);
	zbx_free(os_caption);
	zbx_free(os_csdversion);
	zbx_free(proc_architecture);
	zbx_free(proc_addresswidth);

	return ret;
}