/*
** Copyright (C) 2001-2026 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 "trapper_proxy.h"

#include "../taskmanager/taskmanager_proxy.h"
#include "../proxyconfigwrite/proxyconfigwrite.h"

#include "zbxcomms.h"
#include "zbxcommshigh.h"
#include "zbxtasks.h"
#include "zbxmutexs.h"
#include "zbxdb.h"
#include "zbxdbwrap.h"
#include "zbxproxybuffer.h"
#include "zbxcompress.h"
#include "zbxcacheconfig.h"
#include "zbxjson.h"
#include "zbxrtc.h"
#include "zbx_rtc_constants.h"

#define	LOCK_PROXY_HISTORY	zbx_mutex_lock(proxy_lock)
#define	UNLOCK_PROXY_HISTORY	zbx_mutex_unlock(proxy_lock)

static zbx_mutex_t	proxy_lock = ZBX_MUTEX_NULL;

int	init_proxy_history_lock(unsigned char program_type, char **error)
{
	if (0 != (program_type & ZBX_PROGRAM_TYPE_PROXY_PASSIVE))
		return zbx_mutex_create(&proxy_lock, ZBX_MUTEX_PROXY_HISTORY, error);

	return SUCCEED;
}

void	free_proxy_history_lock(unsigned char program_type)
{
	if (0 != (program_type & ZBX_PROGRAM_TYPE_PROXY_PASSIVE))
		zbx_mutex_destroy(&proxy_lock);
}

static void	active_passive_misconfig(zbx_socket_t *sock, int config_timeout)
{
	char	*msg = NULL;

	msg = zbx_dsprintf(msg, "misconfiguration error: the proxy is running in the active mode but server at \"%s\""
			" sends requests to it as to proxy in passive mode", sock->peer);

	zabbix_log(LOG_LEVEL_WARNING, "%s", msg);
	zbx_send_proxy_response(sock, FAIL, msg, config_timeout);
	zbx_free(msg);
}

/******************************************************************************
 *                                                                            *
 * Purpose: sends data from proxy to server                                   *
 *                                                                            *
 * Parameters: sock            - [IN] connection socket                       *
 *             buffer          - [IN/OUT]                                     *
 *             buffer_size     - [IN]                                         *
 *             reserved        - [IN]                                         *
 *             config_timeout  - [IN]                                         *
 *             error           - [OUT] error message                          *
 *                                                                            *
 ******************************************************************************/
static int	send_data_to_server(zbx_socket_t *sock, char **buffer, size_t buffer_size, size_t reserved,
		int config_timeout, char **error)
{
	if (SUCCEED != zbx_tcp_send_ext(sock, *buffer, buffer_size, reserved, ZBX_TCP_PROTOCOL | ZBX_TCP_COMPRESS,
			config_timeout))
	{
		*error = zbx_strdup(*error, zbx_socket_strerror());
		return FAIL;
	}

	zbx_free(*buffer);

	if (SUCCEED != zbx_recv_response(sock, config_timeout, error))
		return FAIL;

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: sends 'proxy data' request to server                              *
 *                                                                            *
 * Parameters: sock                - [IN] connection socket                   *
 *             ts                  - [IN] connection timestamp                *
 *             config_comms        - [IN] proxy configuration for             *
 *                                        communication with server           *
 *             get_program_type_cb - [IN] callback to get program type        *
 *                                                                            *
 ******************************************************************************/
static void	send_proxy_data(zbx_socket_t *sock, const zbx_timespec_t *ts,
		const zbx_config_comms_args_t *config_comms, zbx_get_program_type_f get_program_type_cb,
		zbx_ipc_async_socket_t *rtc)
{
	struct zbx_json		j;
	zbx_uint64_t		areg_lastid = 0, history_lastid = 0, discovery_lastid = 0;
	char			*error = NULL, *buffer = NULL;
	int			availability_ts, more_history, more_discovery, more_areg, proxy_delay, more;
	zbx_vector_tm_task_t	tasks;
	struct zbx_json_parse	jp, jp_tasks;
	size_t			buffer_size, reserved;

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

	if (SUCCEED != zbx_check_access_passive_proxy(sock, ZBX_DO_NOT_SEND_RESPONSE, "proxy data request",
			config_comms->config_tls, config_comms->config_timeout, config_comms->server))
	{
		/* do not send any reply to server in this case as the server expects proxy data */
		goto out;
	}

	if (0 != (get_program_type_cb() & ZBX_PROGRAM_TYPE_PROXY_PASSIVE))
		LOCK_PROXY_HISTORY;

	zbx_json_init(&j, ZBX_JSON_STAT_BUF_LEN);

	zbx_json_addstring(&j, ZBX_PROTO_TAG_SESSION, zbx_dc_get_session_token(), ZBX_JSON_TYPE_STRING);
	zbx_get_interface_availability_data(&j, &availability_ts);
	zbx_pb_history_get_rows(&j, &history_lastid, &more_history);
	zbx_pb_discovery_get_rows(&j, &discovery_lastid, &more_discovery);
	zbx_pb_autoreg_get_rows(&j, &areg_lastid, &more_areg);
	zbx_proxy_get_host_active_availability(&j);

	zbx_vector_tm_task_create(&tasks);
	zbx_tm_get_remote_tasks(&tasks, 0, 0);

	if (0 != tasks.values_num)
		zbx_tm_json_serialize_tasks(&j, &tasks);

	if (ZBX_PROXY_DATA_MORE == more_history || ZBX_PROXY_DATA_MORE == more_discovery ||
			ZBX_PROXY_DATA_MORE == more_areg)
	{
		zbx_json_adduint64(&j, ZBX_PROTO_TAG_MORE, ZBX_PROXY_DATA_MORE);
		more = ZBX_PROXY_DATA_MORE;
	}
	else
		more = ZBX_PROXY_DATA_DONE;

	zbx_json_addstring(&j, ZBX_PROTO_TAG_VERSION, ZABBIX_VERSION, ZBX_JSON_TYPE_STRING);
	zbx_json_addint64(&j, ZBX_PROTO_TAG_CLOCK, ts->sec);
	zbx_json_addint64(&j, ZBX_PROTO_TAG_NS, ts->ns);

	if (0 != history_lastid && 0 != (proxy_delay = zbx_proxy_get_delay(history_lastid)))
		zbx_json_addint64(&j, ZBX_PROTO_TAG_PROXY_DELAY, proxy_delay);

	if (SUCCEED != zbx_compress(j.buffer, j.buffer_size, &buffer, &buffer_size))
	{
		zabbix_log(LOG_LEVEL_ERR,"cannot compress data: %s", zbx_compress_strerror());
		goto clean;
	}

	reserved = j.buffer_size;
	zbx_json_free(&j);	/* json buffer can be large, free as fast as possible */

	if (SUCCEED == send_data_to_server(sock, &buffer, buffer_size, reserved, config_comms->config_timeout,
			&error))
	{
		zbx_set_availability_diff_ts(availability_ts);

		zbx_db_begin();

		if (0 != history_lastid)
			zbx_pb_set_history_lastid(history_lastid);

		if (0 != discovery_lastid)
			zbx_pb_discovery_set_lastid(discovery_lastid);

		if (0 != areg_lastid)
			zbx_pb_autoreg_set_lastid(areg_lastid);

		if (0 != tasks.values_num)
		{
			zbx_tm_update_task_status(&tasks, ZBX_TM_STATUS_DONE);
			zbx_vector_tm_task_clear_ext(&tasks, zbx_tm_task_free);
		}

		if (SUCCEED == zbx_json_open(sock->buffer, &jp))
		{
			if (SUCCEED == zbx_json_brackets_by_name(&jp, ZBX_PROTO_TAG_TASKS, &jp_tasks))
			{
				zbx_tm_json_deserialize_tasks(&jp_tasks, &tasks);
				zbx_tm_save_tasks(&tasks);
			}
		}

		zbx_db_commit();

		zbx_pb_update_state(more);

		zbx_dc_set_proxy_lastonline(ts->sec);

		if (0 != tasks.values_num)
		{
			zbx_rtc_notify_generic(rtc, ZBX_PROCESS_TYPE_TASKMANAGER, 1, ZBX_RTC_TASK_MANAGER_NOTIFY,
					NULL, 0);
		}
	}
	else
	{
		zabbix_log(LOG_LEVEL_WARNING, "cannot send proxy data to server at \"%s\": %s", sock->peer, error);
		zbx_free(error);
	}
clean:
	zbx_vector_tm_task_clear_ext(&tasks, zbx_tm_task_free);
	zbx_vector_tm_task_destroy(&tasks);

	zbx_json_free(&j);

	if (0 != (get_program_type_cb() & ZBX_PROGRAM_TYPE_PROXY_PASSIVE))
		UNLOCK_PROXY_HISTORY;
out:
	zbx_free(buffer);
	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
}

/******************************************************************************
 *                                                                            *
 * Purpose: sends 'task data' request to server                               *
 *                                                                            *
 * Parameters: sock         - [IN] connection socket                          *
 *             ts           - [IN] connection timestamp                       *
 *             config_comms - [IN] proxy configuration for communication with *
 *                                 server                                     *
 *                                                                            *
 ******************************************************************************/
static void	send_task_data(zbx_socket_t *sock, const zbx_timespec_t *ts,
		const zbx_config_comms_args_t *config_comms, zbx_ipc_async_socket_t *rtc)
{
	struct zbx_json		j;
	char			*error = NULL, *buffer = NULL;
	zbx_vector_tm_task_t	tasks;
	struct zbx_json_parse	jp, jp_tasks;
	size_t			buffer_size, reserved;

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

	if (SUCCEED != zbx_check_access_passive_proxy(sock, ZBX_DO_NOT_SEND_RESPONSE, "proxy data request",
			config_comms->config_tls, config_comms->config_timeout, config_comms->server))
	{
		/* do not send any reply to server in this case as the server expects proxy data */
		goto out;
	}

	zbx_json_init(&j, ZBX_JSON_STAT_BUF_LEN);

	zbx_vector_tm_task_create(&tasks);
	zbx_tm_get_remote_tasks(&tasks, 0, 0);

	if (0 != tasks.values_num)
		zbx_tm_json_serialize_tasks(&j, &tasks);

	zbx_json_addstring(&j, ZBX_PROTO_TAG_VERSION, ZABBIX_VERSION, ZBX_JSON_TYPE_STRING);
	zbx_json_addint64(&j, ZBX_PROTO_TAG_CLOCK, ts->sec);
	zbx_json_addint64(&j, ZBX_PROTO_TAG_NS, ts->ns);

	if (SUCCEED != zbx_compress(j.buffer, j.buffer_size, &buffer, &buffer_size))
	{
		zabbix_log(LOG_LEVEL_ERR,"cannot compress data: %s", zbx_compress_strerror());
		goto clean;
	}

	reserved = j.buffer_size;
	zbx_json_free(&j);	/* json buffer can be large, free as fast as possible */

	if (SUCCEED == send_data_to_server(sock, &buffer, buffer_size, reserved, config_comms->config_timeout,
			&error))
	{
		zbx_db_begin();

		if (0 != tasks.values_num)
		{
			zbx_tm_update_task_status(&tasks, ZBX_TM_STATUS_DONE);
			zbx_vector_tm_task_clear_ext(&tasks, zbx_tm_task_free);
		}

		if (SUCCEED == zbx_json_open(sock->buffer, &jp))
		{
			if (SUCCEED == zbx_json_brackets_by_name(&jp, ZBX_PROTO_TAG_TASKS, &jp_tasks))
			{
				zbx_tm_json_deserialize_tasks(&jp_tasks, &tasks);
				zbx_tm_save_tasks(&tasks);
			}
		}

		zbx_db_commit();

		if (0 != tasks.values_num)
		{
			zbx_rtc_notify_generic(rtc, ZBX_PROCESS_TYPE_TASKMANAGER, 1, ZBX_RTC_TASK_MANAGER_NOTIFY,
					NULL, 0);
		}

		zbx_dc_set_proxy_lastonline(ts->sec);
	}
	else
	{
		zabbix_log(LOG_LEVEL_WARNING, "cannot send task data to server at \"%s\": %s", sock->peer, error);
		zbx_free(error);
	}
clean:
	zbx_vector_tm_task_clear_ext(&tasks, zbx_tm_task_free);
	zbx_vector_tm_task_destroy(&tasks);

	zbx_json_free(&j);
out:
	zbx_free(buffer);
	zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __func__);
}

/*******************************************************************************
 *                                                                             *
 * Purpose: processes proxy specific trapper requests                          *
 *                                                                             *
 * Comments: Some of proxy specific request processing was moved into separate *
 *           library to split server/proxy code dependencies.                  *
 *                                                                             *
 *******************************************************************************/
int	trapper_process_request_proxy(const char *request, zbx_socket_t *sock, const struct zbx_json_parse *jp,
		const zbx_timespec_t *ts, const zbx_config_comms_args_t *config_comms,
		const zbx_config_vault_t *config_vault, int proxydata_frequency,
		zbx_get_program_type_f get_program_type_cb, const zbx_events_funcs_t *events_cbs,
		zbx_get_config_forks_f get_config_forks, const zbx_config_tls_t *config_tls,
		const char *config_frontend_allowed_ip, zbx_ipc_async_socket_t *rtc)
{
	ZBX_UNUSED(jp);
	ZBX_UNUSED(ts);
	ZBX_UNUSED(proxydata_frequency);
	ZBX_UNUSED(events_cbs);
	ZBX_UNUSED(get_config_forks);
	ZBX_UNUSED(config_tls);
	ZBX_UNUSED(config_frontend_allowed_ip);

	if (0 == strcmp(request, ZBX_PROTO_VALUE_PROXY_CONFIG))
	{
		if (0 != (get_program_type_cb() & ZBX_PROGRAM_TYPE_PROXY_PASSIVE))
		{
			zbx_recv_proxyconfig(sock, config_comms->config_tls, config_vault, config_comms->config_timeout,
					config_comms->config_trapper_timeout, config_comms->config_source_ip,
					config_comms->config_ssl_ca_location, config_comms->config_ssl_cert_location,
					config_comms->config_ssl_key_location, config_comms->server);
			return SUCCEED;
		}
		else if (0 != (get_program_type_cb() & ZBX_PROGRAM_TYPE_PROXY_ACTIVE))
		{
			/* This is a misconfiguration: the proxy is configured in active mode */
			/* but server sends requests to it as to a proxy in passive mode. To  */
			/* prevent logging of this problem for every request we report it     */
			/* only when the server sends configuration to the proxy and ignore   */
			/* it for other requests.                                             */
			active_passive_misconfig(sock, config_comms->config_timeout);
			return SUCCEED;
		}
	}
	else if (0 == strcmp(request, ZBX_PROTO_VALUE_PROXY_DATA))
	{
		if (0 != (get_program_type_cb() & ZBX_PROGRAM_TYPE_PROXY_PASSIVE))
		{
			send_proxy_data(sock, ts, config_comms, get_program_type_cb, rtc);
			return SUCCEED;
		}
		return FAIL;
	}
	else if (0 == strcmp(request, ZBX_PROTO_VALUE_PROXY_TASKS))
	{
		if (0 != (get_program_type_cb() & ZBX_PROGRAM_TYPE_PROXY_PASSIVE))
		{
			send_task_data(sock, ts, config_comms, rtc);
			return SUCCEED;
		}
		return FAIL;
	}
	return FAIL;
}
