/*
** 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 "zbxscripts.h"
#include "zbxexpression.h"

#include "zbxexec.h"
#include "zbxdbhigh.h"
#include "zbxtasks.h"
#include "zbxembed.h"
#include "zbxnum.h"
#include "zbxparam.h"
#include "zbxmutexs.h"
#include "zbxshmem.h"
#include "zbx_availability_constants.h"
#include "zbx_scripts_constants.h"
#include "zbxpoller.h"
#ifdef HAVE_OPENIPMI
#include "zbxipmi.h"
#endif
#include "zbxalgo.h"
#include "zbxavailability.h"
#include "zbxcacheconfig.h"
#include "zbxdb.h"
#include "zbxjson.h"
#include "zbxstr.h"
#include "zbxinterface.h"

#define REMOTE_COMMAND_NEW		0
#define REMOTE_COMMAND_RESULT_OOM	1
#define REMOTE_COMMAND_RESULT_WAIT	2
#define REMOTE_COMMAND_COMPLETED	4

static zbx_uint64_t	remote_command_cache_size = 256 * ZBX_KIBIBYTE;

static zbx_mutex_t	remote_commands_lock = ZBX_MUTEX_NULL;
static zbx_shmem_info_t	*remote_commands_mem = NULL;
ZBX_SHMEM_FUNC_IMPL(__remote_commands, remote_commands_mem)
typedef struct
{
	zbx_uint64_t		maxid;
	int			commands_num;
	zbx_hashset_t		commands;
}
zbx_remote_commands_t;

zbx_remote_commands_t	*remote_commands = NULL;

typedef struct
{
	zbx_uint64_t		id;
	zbx_uint64_t		hostid;
	char			*command;
	volatile unsigned char	flag;
	char			*value;
	char			*error;
}
zbx_rc_command_t;

static zbx_hash_t	remote_commands_commands_hash_func(const void *data)
{
	return ZBX_DEFAULT_UINT64_HASH_FUNC(&((const zbx_rc_command_t *)data)->id);
}

static int	remote_commands_commands_compare_func(const void *d1, const void *d2)
{
	const zbx_rc_command_t	*command1 = (const zbx_rc_command_t *)d1;
	const zbx_rc_command_t	*command2 = (const zbx_rc_command_t *)d2;

	ZBX_RETURN_IF_NOT_EQUAL(command1->id, command2->id);

	return 0;
}

/******************************************************************************
 *                                                                            *
 * Purpose: initializes active remote commands cache                          *
 *                                                                            *
 ******************************************************************************/
int	zbx_init_remote_commands_cache(char **error)
{
#define	REMOTE_COMMANS_INITIAL_HASH_SIZE	100
	int		ret = FAIL;
	zbx_uint64_t	size_reserved;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);

	if (SUCCEED != zbx_mutex_create(&remote_commands_lock, ZBX_MUTEX_REMOTE_COMMANDS, error))
		goto out;

	size_reserved = zbx_shmem_required_size(1, "commands cache size", "CommandsCacheSize");

	remote_command_cache_size -= size_reserved;

	if (SUCCEED != zbx_shmem_create(&remote_commands_mem, remote_command_cache_size, "commands cache size",
			"CommandsCacheSize",1, error))
	{
		goto out;
	}

	remote_commands = (zbx_remote_commands_t *)__remote_commands_shmem_malloc_func(NULL,
			sizeof(zbx_remote_commands_t));
	memset(remote_commands, 0, sizeof(zbx_remote_commands_t));

	remote_commands->maxid = 0;
	remote_commands->commands_num = 0;

	zbx_hashset_create_ext(&remote_commands->commands, REMOTE_COMMANS_INITIAL_HASH_SIZE,
			remote_commands_commands_hash_func,remote_commands_commands_compare_func, NULL,
			__remote_commands_shmem_malloc_func, __remote_commands_shmem_realloc_func,
			__remote_commands_shmem_free_func);

	ret = SUCCEED;
out:
	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);

	return ret;
#undef	REMOTE_COMMANS_INITIAL_HASH_SIZE
}

static char	*remote_commands_shared_strdup(const char *str)
{
	char		*new_str;
	zbx_uint64_t	len;

	len = strlen(str) + 1;
	new_str = (char *)__remote_commands_shmem_malloc_func(NULL, len);
	memcpy(new_str, str, len);

	return new_str;
}

/******************************************************************************
 *                                                                            *
 * Purpose: de-initializes active remote commands cache                       *
 *                                                                            *
 ******************************************************************************/
void	zbx_deinit_remote_commands_cache(void)
{
	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);

	if (NULL != remote_commands_mem)
	{
		zbx_hashset_destroy(&remote_commands->commands);

		zbx_shmem_destroy(remote_commands_mem);
		remote_commands_mem = NULL;
		zbx_mutex_destroy(&remote_commands_lock);
	}

	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
}

/******************************************************************************
 *                                                                            *
 * Purpose: locks remote_commands cache                                       *
 *                                                                            *
 ******************************************************************************/
static void	commands_lock(void)
{
	zbx_mutex_lock(remote_commands_lock);
}

/******************************************************************************
 *                                                                            *
 * Purpose: unlocks remote_commands cache                                     *
 *                                                                            *
 ******************************************************************************/
static void	commands_unlock(void)
{
	zbx_mutex_unlock(remote_commands_lock);
}

static int	remote_commands_insert_result(zbx_uint64_t id, char *value, char *err_msg)
{
	int			ret = SUCCEED;
	zbx_rc_command_t	*command, command_loc;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);

	commands_lock();
	command_loc.id = id;

	if (NULL != (command = (zbx_rc_command_t *)zbx_hashset_search(&remote_commands->commands,
			&command_loc)))
	{
		if (NULL != value && NULL == (command->value = remote_commands_shared_strdup(value)))
		{
			command->flag |= REMOTE_COMMAND_RESULT_OOM;
			ret = FAIL;
		}

		if (NULL != err_msg && NULL == (command->error = remote_commands_shared_strdup(err_msg)))
		{
			command->flag |= REMOTE_COMMAND_RESULT_OOM;
			ret = FAIL;
		}

		command->flag |= REMOTE_COMMAND_COMPLETED;
	}

	commands_unlock();
	zabbix_log(LOG_LEVEL_DEBUG, "End of %s(), ret %d", __func__, ret);

	return ret;
}

void	zbx_process_command_results(struct zbx_json_parse *jp)
{
	int			values_num = 0, parsed_num = 0, results_num = 0;
	const char		*pnext = NULL;
	struct zbx_json_parse	jp_commands, jp_command;
	char			*str = NULL, *value = NULL, *error = NULL;
	size_t			str_alloc = 0;
	zbx_uint64_t		id;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);

	if (SUCCEED != zbx_json_brackets_by_name(jp, ZBX_PROTO_TAG_COMMANDS, &jp_commands))
		goto out;

	while (NULL != (pnext = zbx_json_next(&jp_commands, pnext)))
	{
		if (FAIL == zbx_json_brackets_open(pnext, &jp_command))
		{
			zabbix_log(LOG_LEVEL_WARNING, "%s", zbx_json_strerror());
			goto out;
		}

		parsed_num++;
		str_alloc = 0;
		zbx_free(str);

		if (SUCCEED != zbx_json_value_by_name_dyn(&jp_command, ZBX_PROTO_TAG_ID, &str, &str_alloc, NULL))
			continue;

		if (SUCCEED != zbx_is_uint64(str, &id))
		{
			zabbix_log(LOG_LEVEL_WARNING, "Wrong command id '%s'", str);
			goto out;
		}

		str_alloc = 0;
		zbx_free(value);
		zbx_free(error);

		if (SUCCEED != zbx_json_value_by_name_dyn(&jp_command, ZBX_PROTO_TAG_VALUE, &value, &str_alloc, NULL))
		{

			if (SUCCEED != zbx_json_value_by_name_dyn(&jp_command, ZBX_PROTO_TAG_ERROR, &error, &str_alloc,
					NULL))
			{
				continue;
			}
		}

		values_num++;
		if (SUCCEED == remote_commands_insert_result(id, value, error))
			results_num++;
	}
out:
	zbx_free(str);
	zbx_free(value);
	zbx_free(error);
	zabbix_log(LOG_LEVEL_DEBUG, "End of %s(), parsed %d values received %d results inserted %d", __func__,
			parsed_num, values_num, results_num);
}

void	zbx_remote_commands_prepare_to_send(struct zbx_json *json, zbx_uint64_t hostid, int config_timeout)
{
	zbx_hashset_iter_t	iter_comands;
	zbx_rc_command_t	*command;
	int			has_commands = 0;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);

	commands_lock();

	if (0 == remote_commands->commands_num)
		goto out;

	zbx_hashset_iter_reset(&remote_commands->commands, &iter_comands);

	while (NULL != (command = (zbx_rc_command_t *)zbx_hashset_iter_next(&iter_comands)))
	{
		if (hostid == command->hostid)
		{
			int	wait = 0;

			if (0 == has_commands)
			{
				zbx_json_addarray(json, ZBX_PROTO_TAG_COMMANDS);
				has_commands = 1;
			}

			zbx_json_addobject(json, NULL);
			zbx_json_addstring(json, ZBX_PROTO_TAG_COMMAND, command->command, ZBX_JSON_TYPE_STRING);
			zbx_json_adduint64(json, ZBX_PROTO_TAG_ID, command->id);

			if (0 != (command->flag & REMOTE_COMMAND_RESULT_WAIT))
				wait = 1;

			zbx_json_adduint64(json, ZBX_PROTO_TAG_WAIT, (zbx_uint64_t)wait);

			zbx_json_adduint64(json, ZBX_PROTO_TAG_TIMEOUT, (zbx_uint64_t)config_timeout);

			zbx_json_close(json);

			remote_commands->commands_num--;
		}
	}

	if (0 != has_commands)
		zbx_json_close(json);
out:
	commands_unlock();

	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
}

static int	active_command_send_and_result_fetch(const zbx_dc_host_t *host, const char *command, char **result,
		int config_timeout, zbx_get_config_forks_f get_config_forks, char *error, size_t max_error_len)
{
	int			ret = FAIL, completed = 0;
	zbx_rc_command_t	cmd, *pcmd;
	time_t			time_start;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);

	if (2 > get_config_forks(ZBX_PROCESS_TYPE_TRAPPER) && NULL != result)
	{
		zbx_snprintf(error, max_error_len, "cannot execute remote command on active agent, at least two"
				" trappers are required");
		goto out;
	}

	*error = '\0';
	commands_lock();

	cmd.id = remote_commands->maxid++;

	if (NULL == (pcmd = zbx_hashset_insert(&remote_commands->commands, &cmd, sizeof(cmd))))
	{
		commands_unlock();
		zbx_snprintf(error, max_error_len, "cannot allocate memory for remote command");
		goto out;
	}

	pcmd->flag = REMOTE_COMMAND_NEW;
	if (NULL != result)
		pcmd->flag |= REMOTE_COMMAND_RESULT_WAIT;

	pcmd->hostid = host->hostid;
	pcmd->command = remote_commands_shared_strdup(command);
	pcmd->value = NULL;
	pcmd->error = NULL;

	if (NULL == pcmd->command)
	{
		zbx_hashset_remove_direct(&remote_commands->commands, pcmd);
		zbx_snprintf(error, max_error_len, "cannot allocate memory for remote command");
		commands_unlock();
		goto out;
	}

	remote_commands->commands_num++;
	commands_unlock();

	for (time_start = time(NULL); config_timeout > time(NULL) - time_start; sleep(1))
	{
		if  (0 != (REMOTE_COMMAND_COMPLETED & pcmd->flag))
		{
			commands_lock();

			if (0 == (REMOTE_COMMAND_COMPLETED & pcmd->flag))
			{
				commands_unlock();
				continue;
			}

			completed = 1;
			break;
		}
	}

	if (0 == completed)
	{
		zbx_snprintf(error, max_error_len, "timeout while retrieving result for remote command");
		commands_lock();
	}
	else  if (0 != (REMOTE_COMMAND_RESULT_OOM & pcmd->flag))
	{
		zbx_snprintf(error, max_error_len, "cannot allocate memory for remote command result");
	}
	else if (NULL != pcmd->value)
	{
		if (NULL != result)
			*result = zbx_strdup(*result, pcmd->value);

		__remote_commands_shmem_free_func(pcmd->value);
		ret = SUCCEED;
	}
	else if (NULL != pcmd->error)
	{
		zbx_strlcpy(error, pcmd->error, max_error_len);
		__remote_commands_shmem_free_func(pcmd->error);
	}

	__remote_commands_shmem_free_func(pcmd->command);
	zbx_hashset_remove_direct(&remote_commands->commands, pcmd);

	commands_unlock();
out:
	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret));

	return ret;
}

static int	passive_command_send_and_result_fetch(const zbx_dc_host_t *host, const char *command, char **result,
		int config_timeout, const char *config_source_ip, unsigned char program_type, char *error,
		size_t max_error_len)
{
	int		ret;
	AGENT_RESULT	agent_result;
	char		*param = NULL, *port = NULL;
	zbx_dc_item_t	item;
	int		version;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);

	*error = '\0';
	memset(&item, 0, sizeof(item));
	memcpy(&item.host, host, sizeof(item.host));

	if (SUCCEED != (ret = zbx_dc_config_get_interface_by_type(&item.interface, host->hostid, INTERFACE_TYPE_AGENT)))
	{
		zbx_snprintf(error, max_error_len, "Zabbix agent interface is not defined for host [%s]", host->host);
		goto fail;
	}

	port = zbx_strdup(port, item.interface.port_orig);
	zbx_substitute_simple_macros(NULL, NULL, NULL, NULL, &host->hostid, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
			&port, ZBX_MACRO_TYPE_COMMON, NULL, 0);

	if (SUCCEED != (ret = zbx_is_ushort(port, &item.interface.port)))
	{
		zbx_snprintf(error, max_error_len, "Invalid port number [%s]", item.interface.port_orig);
		goto fail;
	}

	param = zbx_strdup(param, command);
	if (SUCCEED != (ret = zbx_quote_key_param(&param, 0)))
	{
		zbx_snprintf(error, max_error_len, "Invalid param [%s]", param);
		goto fail;
	}

	item.key = zbx_dsprintf(item.key, "system.run[%s%s]", param, NULL == result ? ",nowait" : "");
	item.value_type = ITEM_VALUE_TYPE_TEXT;
	item.timeout = config_timeout;

	zbx_init_agent_result(&agent_result);

	version = item.interface.version;
	if (SUCCEED != (ret = zbx_agent_get_value(&item, config_source_ip, program_type, &agent_result, &version)))
	{
		if (ZBX_ISSET_MSG(&agent_result))
			zbx_strlcpy(error, agent_result.msg, max_error_len);
		ret = FAIL;
	}
	else if (NULL != result && ZBX_ISSET_TEXT(&agent_result))
		*result = zbx_strdup(*result, agent_result.text);

	zbx_free_agent_result(&agent_result);

	zbx_free(item.key);

	if (version != item.interface.version)
		zbx_dc_set_interface_version(item.interface.interfaceid, version);
fail:
	zbx_free(port);
	zbx_free(param);

	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret));

	return ret;
}

static int	zbx_execute_script_on_agent(const zbx_dc_host_t *host, const char *command, char **result,
		int config_timeout, const char *config_source_ip, zbx_get_config_forks_f get_config_forks,
		unsigned char program_type, char *error, size_t max_error_len)
{
	zbx_dc_interface_t	interface;

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

	if (FAIL == zbx_dc_config_get_interface_by_type(&interface, host->hostid, INTERFACE_TYPE_AGENT))
		zabbix_log(LOG_LEVEL_DEBUG, "cannot find agent interface on host \"%s\"", host->host);

	if (ZBX_INTERFACE_AVAILABLE_TRUE != interface.available &&
			ZBX_INTERFACE_AVAILABLE_TRUE == zbx_get_active_agent_availability(host->hostid))
	{
		return active_command_send_and_result_fetch(host, command, result, config_timeout, get_config_forks,
				error, max_error_len);
	}

	return passive_command_send_and_result_fetch(host, command, result, config_timeout, config_source_ip,
			program_type, error, max_error_len);
}

static int	zbx_execute_script_on_terminal(const zbx_dc_host_t *host, const zbx_script_t *script, char **result,
		int config_timeout, const char *config_source_ip, const char *config_ssh_key_location, char *error,
		size_t max_error_len)
{
	int		ret = FAIL;
	AGENT_RESULT	agent_result;
	zbx_dc_item_t	item;
	int		(*function)(zbx_dc_item_t *, const char*, const char *config_ssh_key_location, AGENT_RESULT *);

#if defined(HAVE_SSH2) || defined(HAVE_SSH)
	assert(ZBX_SCRIPT_TYPE_SSH == script->type || ZBX_SCRIPT_TYPE_TELNET == script->type);
#else
	assert(ZBX_SCRIPT_TYPE_TELNET == script->type);
#endif

	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);

	*error = '\0';
	memset(&item, 0, sizeof(item));
	memcpy(&item.host, host, sizeof(item.host));

	for (int i = 0; INTERFACE_TYPE_COUNT > i; i++)
	{
		if (SUCCEED == (ret = zbx_dc_config_get_interface_by_type(&item.interface, host->hostid,
				zbx_get_interface_type_priority(i))))
		{
			break;
		}
	}

	if (FAIL == ret)
	{
		zbx_snprintf(error, max_error_len, "No interface defined for host [%s]", host->host);
		goto fail;
	}

	switch (script->type)
	{
		case ZBX_SCRIPT_TYPE_SSH:
			item.authtype = script->authtype;
			item.publickey = script->publickey;
			item.privatekey = script->privatekey;
			ZBX_FALLTHROUGH;
		case ZBX_SCRIPT_TYPE_TELNET:
			item.username = script->username;
			item.password = script->password;
			break;
	}

#if defined(HAVE_SSH2) || defined(HAVE_SSH)
	if (ZBX_SCRIPT_TYPE_SSH == script->type)
	{
		item.key = zbx_dsprintf(item.key, "ssh.run[,,%s]", script->port);
		function = zbx_ssh_get_value;
	}
	else
	{
#endif
		item.key = zbx_dsprintf(item.key, "telnet.run[,,%s]", script->port);
		function = zbx_telnet_get_value;
#if defined(HAVE_SSH2) || defined(HAVE_SSH)
	}
#endif
	item.value_type = ITEM_VALUE_TYPE_TEXT;
	item.params = zbx_strdup(item.params, script->command);
	item.timeout = config_timeout;

	zbx_init_agent_result(&agent_result);

	if (SUCCEED != (ret = function(&item, config_source_ip, config_ssh_key_location, &agent_result)))
	{
		if (ZBX_ISSET_MSG(&agent_result))
			zbx_strlcpy(error, agent_result.msg, max_error_len);
		ret = FAIL;
	}
	else if (NULL != result && ZBX_ISSET_TEXT(&agent_result))
		*result = zbx_strdup(*result, agent_result.text);

	zbx_free_agent_result(&agent_result);

	zbx_free(item.params);
	zbx_free(item.key);
fail:
	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret));

	return ret;
}

int	zbx_check_script_permissions(zbx_uint64_t groupid, zbx_uint64_t hostid)
{
	zbx_db_result_t		result;
	int			ret = SUCCEED;
	zbx_vector_uint64_t	groupids;
	char			*sql = NULL;
	size_t			sql_alloc = 0, sql_offset = 0;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s() groupid:" ZBX_FS_UI64 " hostid:" ZBX_FS_UI64, __func__, groupid, hostid);

	if (0 == groupid)
		goto exit;

	zbx_vector_uint64_create(&groupids);
	zbx_dc_get_nested_hostgroupids(&groupid, 1, &groupids);

	zbx_snprintf_alloc(&sql, &sql_alloc, &sql_offset,
			"select hostid"
			" from hosts_groups"
			" where hostid=" ZBX_FS_UI64
				" and",
			hostid);

	zbx_db_add_condition_alloc(&sql, &sql_alloc, &sql_offset, "groupid", groupids.values,
			groupids.values_num);

	result = zbx_db_select("%s", sql);

	zbx_free(sql);
	zbx_vector_uint64_destroy(&groupids);

	if (NULL == zbx_db_fetch(result))
		ret = FAIL;

	zbx_db_free_result(result);
exit:
	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret));

	return ret;
}

int	zbx_check_script_user_permissions(zbx_uint64_t userid, zbx_uint64_t hostid, zbx_script_t *script)
{
	int		ret = SUCCEED;
	zbx_db_result_t	result;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s() userid:" ZBX_FS_UI64 " hostid:" ZBX_FS_UI64 " scriptid:" ZBX_FS_UI64,
			__func__, userid, hostid, script->scriptid);

	result = zbx_db_select(
		"select null"
			" from host_hgset h,permission p,user_ugset u"
		" where u.ugsetid=p.ugsetid"
			" and p.hgsetid=h.hgsetid"
			" and h.hostid=" ZBX_FS_UI64
			" and u.userid=" ZBX_FS_UI64
			" and p.permission>=%d",
		hostid,
		userid,
		script->host_access);

	if (NULL == zbx_db_fetch(result))
		ret = FAIL;

	zbx_db_free_result(result);

	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret));

	return ret;
}

void	zbx_script_init(zbx_script_t *script)
{
	memset(script, 0, sizeof(zbx_script_t));
}

void	zbx_script_clean(zbx_script_t *script)
{
	zbx_free(script->port);
	zbx_free(script->username);
	zbx_free(script->publickey);
	zbx_free(script->privatekey);
	zbx_free(script->password);
	zbx_free(script->name);
	zbx_free(script->command);
	zbx_free(script->command_orig);
	zbx_free(script->manualinput_validator);
}

/******************************************************************************
 *                                                                            *
 * Purpose: pack webhook script parameters into JSON                          *
 *                                                                            *
 * Parameters: params      - [IN] vector of pairs of pointers to parameter    *
 *                                names and values                            *
 *             params_json - [OUT] JSON string                                *
 *                                                                            *
 ******************************************************************************/
void	zbx_webhook_params_pack_json(const zbx_vector_ptr_pair_t *params, char **params_json)
{
	struct zbx_json	json_data;
	int		i;

	zbx_json_init(&json_data, ZBX_JSON_STAT_BUF_LEN);

	for (i = 0; i < params->values_num; i++)
	{
		zbx_ptr_pair_t	pair = params->values[i];

		zbx_json_addstring(&json_data, pair.first, pair.second, ZBX_JSON_TYPE_STRING);
	}

	zbx_json_close(&json_data);
	*params_json = zbx_strdup(*params_json, json_data.buffer);
	zbx_json_free(&json_data);
}

/***********************************************************************************
 *                                                                                 *
 * Purpose: prepares user script                                                   *
 *                                                                                 *
 * Parameters: script        - [IN] script to prepare                              *
 *             hostid        - [IN] host the script will be executed on            *
 *             error         - [OUT] error message buffer                          *
 *             max_error_len - [IN] size of error message output buffer            *
 *                                                                                 *
 * Return value:  SUCCEED - script has been prepared successfully                  *
 *                FAIL    - otherwise, error contains error message                *
 *                                                                                 *
 * Comments: This function prepares script for execution by loading global         *
 *           script/expanding macros (except in script body).                      *
 *           Prepared scripts must be always freed with zbx_script_clean()         *
 *           function.                                                             *
 *                                                                                 *
 ***********************************************************************************/
int	zbx_script_prepare(zbx_script_t *script, const zbx_uint64_t *hostid, char *error, size_t max_error_len)
{
	int			ret = FAIL;
	zbx_dc_um_handle_t	*um_handle;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);

	um_handle = zbx_dc_open_user_macros();

	switch (script->type)
	{
		case ZBX_SCRIPT_TYPE_SSH:
			zbx_substitute_simple_macros(NULL, NULL, NULL, NULL, hostid, NULL, NULL, NULL, NULL, NULL, NULL,
					NULL, &script->publickey, ZBX_MACRO_TYPE_COMMON, NULL, 0);
			zbx_substitute_simple_macros(NULL, NULL, NULL, NULL, hostid, NULL, NULL, NULL, NULL, NULL, NULL,
					NULL, &script->privatekey, ZBX_MACRO_TYPE_COMMON, NULL, 0);
			ZBX_FALLTHROUGH;
		case ZBX_SCRIPT_TYPE_TELNET:
			zbx_substitute_simple_macros(NULL, NULL, NULL, NULL, hostid, NULL, NULL, NULL, NULL, NULL, NULL,
					NULL, &script->port, ZBX_MACRO_TYPE_COMMON, NULL, 0);

			if ('\0' != *script->port && SUCCEED != (ret = zbx_is_ushort(script->port, NULL)))
			{
				zbx_snprintf(error, max_error_len, "Invalid port number \"%s\"", script->port);
				goto out;
			}

			zbx_substitute_simple_macros_unmasked(NULL, NULL, NULL, NULL, hostid, NULL, NULL, NULL, NULL,
					NULL, NULL, NULL, &script->username, ZBX_MACRO_TYPE_COMMON, NULL, 0);
			zbx_substitute_simple_macros_unmasked(NULL, NULL, NULL, NULL, hostid, NULL, NULL, NULL, NULL,
					NULL, NULL, NULL, &script->password, ZBX_MACRO_TYPE_COMMON, NULL, 0);
			break;
		case ZBX_SCRIPT_TYPE_CUSTOM_SCRIPT:
			zbx_dos2unix(script->command);	/* CR+LF (Windows) => LF (Unix) */
			break;
		case ZBX_SCRIPT_TYPE_WEBHOOK:
		case ZBX_SCRIPT_TYPE_IPMI:
			break;
		default:
			zbx_snprintf(error, max_error_len, "Invalid command type \"%d\".", (int)script->type);
			goto out;
	}

	zbx_dc_close_user_macros(um_handle);

	ret = SUCCEED;
out:
	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret));
	return ret;
}

/******************************************************************************
 *                                                                            *
 * Purpose: fetch webhook parameters                                          *
 *                                                                            *
 * Parameters:  scriptid  - [IN] id of script to be executed                  *
 *              params    - [OUT] parameters, name-value pairs                *
 *              error     - [IN/OUT] error message                            *
 *              error_len - [IN] maximum error length                         *
 *                                                                            *
 * Return value:  SUCCEED - processed successfully                            *
 *                FAIL - error occurred                                       *
 *                                                                            *
 ******************************************************************************/
int	zbx_db_fetch_webhook_params(zbx_uint64_t scriptid, zbx_vector_ptr_pair_t *params, char *error, size_t error_len)
{
	int		ret = SUCCEED;
	zbx_db_result_t	result;
	zbx_db_row_t	row;
	zbx_ptr_pair_t	pair;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s() scriptid:" ZBX_FS_UI64, __func__, scriptid);

	result = zbx_db_select("select name,value from script_param where scriptid=" ZBX_FS_UI64, scriptid);

	if (NULL == result)
	{
		zbx_strlcpy(error, "Database error, cannot get webhook script parameters.", error_len);
		ret = FAIL;
		goto out;
	}

	while (NULL != (row = zbx_db_fetch(result)))
	{
		pair.first = zbx_strdup(NULL, row[0]);
		pair.second = zbx_strdup(NULL, row[1]);
		zbx_vector_ptr_pair_append(params, pair);
	}

	zbx_db_free_result(result);
out:
	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret));

	return ret;
}

/****************************************************************************************
 *                                                                                      *
 * Purpose: executes user scripts or remote commands                                    *
 *                                                                                      *
 * Parameters:  script                       - [IN] script to be executed               *
 *              host                         - [IN] host the script will be executed on *
 *              params                       - [IN] parameters for the script           *
 *              config_timeout               - [IN]                                     *
 *              config_trapper_timeout       - [IN]                                     *
 *              config_source_ip             - [IN]                                     *
 *              config_ssh_key_location      - [IN]                                     *
 *              config_enable_global_scripts - [IN]                                     *
 *              get_config_forks             - [IN]                                     *
 *              result                       - [OUT] result of a script execution       *
 *              error                        - [OUT] error reported by the script       *
 *              max_error_len                - [IN] maximum error length                *
 *              debug                        - [OUT] debug data (optional)              *
 *                                                                                      *
 * Return value:  SUCCEED - processed successfully                                      *
 *                FAIL - error occurred                                                 *
 *                TIMEOUT_ERROR - timeout occurred                                      *
 *                                                                                      *
 ***************************************************************************************/
int	zbx_script_execute(const zbx_script_t *script, const zbx_dc_host_t *host, const char *params,
		int config_timeout, int config_trapper_timeout, const char *config_source_ip,
		const char *config_ssh_key_location, int config_enable_global_scripts,
		zbx_get_config_forks_f get_config_forks, unsigned char program_type, char **result, char *error,
		size_t max_error_len, char **debug)
{
	int	ret = FAIL;

	zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __func__);

	*error = '\0';

	switch (script->type)
	{
		case ZBX_SCRIPT_TYPE_WEBHOOK:
			ret = zbx_es_execute_command(script->command, params, script->timeout, config_source_ip,
					result, error, max_error_len, debug);
			break;
		case ZBX_SCRIPT_TYPE_CUSTOM_SCRIPT:
			switch (script->execute_on)
			{
				case ZBX_SCRIPT_EXECUTE_ON_AGENT:
					ret = zbx_execute_script_on_agent(host, script->command, result, config_timeout,
							config_source_ip, get_config_forks, program_type, error,
							max_error_len);
					break;
				case ZBX_SCRIPT_EXECUTE_ON_SERVER:
				case ZBX_SCRIPT_EXECUTE_ON_PROXY:
					if (0 == config_enable_global_scripts)
					{
						zbx_snprintf(error, max_error_len, "Global script execution on Zabbix "
								"server is disabled by server configuration");
						ret = FAIL;
					}
					else if (SUCCEED != (ret = zbx_execute(script->command, result, error, max_error_len,
							config_trapper_timeout, ZBX_EXIT_CODE_CHECKS_ENABLED, NULL)))
					{
						ret = FAIL;
					}
					break;
				default:
					zbx_snprintf(error, max_error_len, "Invalid 'Execute on' option \"%d\".",
							(int)script->execute_on);
			}
			break;
		case ZBX_SCRIPT_TYPE_IPMI:
#ifdef HAVE_OPENIPMI
			if (0 == get_config_forks(ZBX_PROCESS_TYPE_IPMIPOLLER))
			{
				zbx_strlcpy(error, "Cannot perform IPMI request: configuration parameter"
						" \"StartIPMIPollers\" is 0.", max_error_len);
				break;
			}

			if (SUCCEED == (ret = zbx_ipmi_execute_command(host, script->command, error, max_error_len)))
			{
				if (NULL != result)
					*result = zbx_strdup(*result, "IPMI command successfully executed.");
			}
#else
			zbx_strlcpy(error, "Support for IPMI commands was not compiled in.", max_error_len);
#endif
			break;
		case ZBX_SCRIPT_TYPE_SSH:
#if !defined(HAVE_SSH2) && !defined(HAVE_SSH)
			zbx_strlcpy(error, "Support for SSH script was not compiled in.", max_error_len);
			break;
#endif
		case ZBX_SCRIPT_TYPE_TELNET:
			ret = zbx_execute_script_on_terminal(host, script, result, config_timeout, config_source_ip,
					config_ssh_key_location, error, max_error_len);
			break;
		default:
			zbx_snprintf(error, max_error_len, "Invalid command type \"%d\".", (int)script->type);
	}

	if (SUCCEED != ret && NULL != result)
		*result = zbx_strdup(*result, "");

	zabbix_log(LOG_LEVEL_DEBUG, "End of %s():%s", __func__, zbx_result_string(ret));

	return ret;
}

/******************************************************************************
 *                                                                            *
 * Purpose: creates remote command task from script                           *
 *                                                                            *
 * Return value:  identifier of created task or 0 in case of error            *
 *                                                                            *
 ******************************************************************************/
zbx_uint64_t	zbx_script_create_task(const zbx_script_t *script, const zbx_dc_host_t *host, zbx_uint64_t alertid,
		int now)
{
	zbx_tm_task_t	*task;
	unsigned short	port;
	zbx_uint64_t	taskid;

	if (NULL != script->port && '\0' != script->port[0])
		zbx_is_ushort(script->port, &port);
	else
		port = 0;

	zbx_db_begin();

	taskid = zbx_db_get_maxid("task");

	task = zbx_tm_task_create(taskid, ZBX_TM_TASK_REMOTE_COMMAND, ZBX_TM_STATUS_NEW, now,
			ZBX_REMOTE_COMMAND_TTL, host->proxyid);

	task->data = zbx_tm_remote_command_create(script->type, script->command, script->execute_on, port,
			script->authtype, script->username, script->password, script->publickey, script->privatekey,
			taskid, host->hostid, alertid);

	if (FAIL == zbx_tm_save_task(task))
		taskid = 0;

	zbx_db_commit();

	zbx_tm_task_free(task);

	return taskid;
}