/* server_ban.* RPC calls
 * (C) Copyright 2022-.. Bram Matthys (Syzop) and the UnrealIRCd team
 * License: GPLv2 or later
 */

#include "unrealircd.h"

ModuleHeader MOD_HEADER
= {
	"rpc/server_ban",
	"1.0.3",
	"server_ban.* RPC calls",
	"UnrealIRCd Team",
	"unrealircd-6",
};

/* Forward declarations */
RPC_CALL_FUNC(rpc_server_ban_list);
RPC_CALL_FUNC(rpc_server_ban_get);
RPC_CALL_FUNC(rpc_server_ban_del);
RPC_CALL_FUNC(rpc_server_ban_add);

MOD_INIT()
{
	RPCHandlerInfo r;

	MARK_AS_OFFICIAL_MODULE(modinfo);

	memset(&r, 0, sizeof(r));
	r.method = "server_ban.list";
	r.loglevel = ULOG_DEBUG;
	r.call = rpc_server_ban_list;
	if (!RPCHandlerAdd(modinfo->handle, &r))
	{
		config_error("[rpc/server_ban] Could not register RPC handler");
		return MOD_FAILED;
	}
	r.method = "server_ban.get";
	r.loglevel = ULOG_DEBUG;
	r.call = rpc_server_ban_get;
	if (!RPCHandlerAdd(modinfo->handle, &r))
	{
		config_error("[rpc/server_ban] Could not register RPC handler");
		return MOD_FAILED;
	}
	r.method = "server_ban.del";
	r.call = rpc_server_ban_del;
	if (!RPCHandlerAdd(modinfo->handle, &r))
	{
		config_error("[rpc/server_ban] Could not register RPC handler");
		return MOD_FAILED;
	}
	r.method = "server_ban.add";
	r.call = rpc_server_ban_add;
	if (!RPCHandlerAdd(modinfo->handle, &r))
	{
		config_error("[rpc/server_ban] Could not register RPC handler");
		return MOD_FAILED;
	}

	return MOD_SUCCESS;
}

MOD_LOAD()
{
	return MOD_SUCCESS;
}

MOD_UNLOAD()
{
	return MOD_SUCCESS;
}

RPC_CALL_FUNC(rpc_server_ban_list)
{
	json_t *result, *list, *item;
	int index, index2;
	TKL *tkl;

	result = json_object();
	list = json_array();
	json_object_set_new(result, "list", list);

	for (index = 0; index < TKLIPHASHLEN1; index++)
	{
		for (index2 = 0; index2 < TKLIPHASHLEN2; index2++)
		{
			for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = tkl->next)
			{
				if (TKLIsServerBan(tkl))
				{
					item = json_object();
					json_expand_tkl(item, NULL, tkl, 1);
					json_array_append_new(list, item);
				}
			}
		}
	}
	for (index = 0; index < TKLISTLEN; index++)
	{
		for (tkl = tklines[index]; tkl; tkl = tkl->next)
		{
			if (TKLIsServerBan(tkl))
			{
				item = json_object();
				json_expand_tkl(item, NULL, tkl, 1);
				json_array_append_new(list, item);
			}
		}
	}

	rpc_response(client, request, result);
	json_decref(result);
}

/** Shared code for selecting a server ban, for .add/.del/.get */
int server_ban_select_criteria(Client *client, json_t *request, json_t *params, int add,
                               const char **name,
                               const char **type_name,
                               char *tkl_type_char,
                               int *tkl_type_int,
                               char **usermask,
                               char **hostmask,
                               int *soft)
{
	const char *error;

	*name = json_object_get_string(params, "name");
	if (!*name)
	{
		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: 'name'");
		return 0;
	}

	*type_name = json_object_get_string(params, "type");
	if (!*type_name)
	{
		rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: 'type'");
		return 0;
	}

	*tkl_type_char = tkl_configtypetochar(*type_name);
	if (!*tkl_type_char)
	{
		rpc_error_fmt(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Invalid type: '%s'", *type_name);
		return 0;
	}
	*tkl_type_int = tkl_chartotype(*tkl_type_char);
	if (!TKLIsServerBanType(*tkl_type_int))
	{
		rpc_error_fmt(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Invalid type: '%s' (type exists but is not valid for in server_ban.*)", *type_name);
		return 0;
	}

	if (!server_ban_parse_mask(client, add, *tkl_type_char, *name, usermask, hostmask, soft, &error))
	{
		rpc_error_fmt(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Error: %s", error);
		return 0;
	}

	/* Hm, shouldn't this be done by server_ban_parse_mask() ? */
	if (*soft && (*usermask[0] == '%'))
		*usermask = *usermask + 1;

	return 1;
}

RPC_CALL_FUNC(rpc_server_ban_get)
{
	json_t *result, *list, *item;
	const char *name, *type_name;
	char *usermask, *hostmask;
	int soft;
	TKL *tkl;
	char tkl_type_char;
	int tkl_type_int;

	if (!server_ban_select_criteria(client, request, params, 0,
	                                &name, &type_name,
	                                &tkl_type_char, &tkl_type_int,
	                                &usermask, &hostmask, &soft))
	{
		return;
	}

	if (!(tkl = find_tkl_serverban(tkl_type_int, usermask, hostmask, soft)))
	{
		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Ban not found");
		return;
	}

	result = json_object();
	json_expand_tkl(result, "tkl", tkl, 1);
	rpc_response(client, request, result);
	json_decref(result);
}

RPC_CALL_FUNC(rpc_server_ban_del)
{
	json_t *result, *list, *item;
	const char *name, *type_name;
	const char *set_by;
	char *usermask, *hostmask;
	char usermask_mess[256];
	int soft;
	TKL *tkl;
	char tkl_type_char;
	int tkl_type_int;
	const char *tkllayer[10];
	char tkl_type_str[2];

	if (!server_ban_select_criteria(client, request, params, 0,
	                                &name, &type_name,
	                                &tkl_type_char, &tkl_type_int,
	                                &usermask, &hostmask, &soft))
	{
		return;
	}

	tkl_type_str[0] = tkl_type_char;
	tkl_type_str[1] = '\0';

	if (!(tkl = find_tkl_serverban(tkl_type_int, usermask, hostmask, soft)))
	{
		rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Ban not found");
		return;
	}

	OPTIONAL_PARAM_STRING("set_by", set_by);
	if (!set_by)
		set_by = client->name;

	result = json_object();
	json_expand_tkl(result, "tkl", tkl, 1);

	tkllayer[1] = "-";
	tkllayer[2] = tkl_type_str;
	if (soft)
	{
		/* I don't like this fiddling.
		 * It will be gone when we move from cmd_tkl() to a real function though.
		 */
		snprintf(usermask_mess, sizeof(usermask_mess), "%%%s", usermask);
		tkllayer[3] = usermask_mess;
	} else {
		tkllayer[3] = usermask;
	}
	tkllayer[4] = hostmask;
	tkllayer[5] = set_by;
	tkllayer[6] = NULL;
	cmd_tkl(&me, NULL, 6, tkllayer);

	if (!find_tkl_serverban(tkl_type_int, usermask, hostmask, soft))
	{
		rpc_response(client, request, result);
	} else {
		/* Actually this may not be an internal error, it could be an
		 * incorrect request, such as asking to remove a config-based ban.
		 */
		rpc_error(client, request, JSON_RPC_ERROR_INTERNAL_ERROR, "Unable to remove item");
	}
	json_decref(result);
}

RPC_CALL_FUNC(rpc_server_ban_add)
{
	json_t *result, *list, *item;
	const char *name, *type_name;
	const char *set_by;
	char *usermask, *hostmask;
	int soft;
	TKL *tkl;
	char tkl_type_char;
	int tkl_type_int;
	char tkl_type_str[2];
	const char *reason;
	const char *str;
	time_t tkl_expire_at;
	time_t tkl_set_at = TStime();

	if (!server_ban_select_criteria(client, request, params, 1,
	                                &name, &type_name,
	                                &tkl_type_char, &tkl_type_int,
	                                &usermask, &hostmask, &soft))
	{
		return;
	}

	tkl_type_str[0] = tkl_type_char;
	tkl_type_str[1] = '\0';

	REQUIRE_PARAM_STRING("reason", reason);

	/* Duration / expiry time */
	if ((str = json_object_get_string(params, "duration_string")))
	{
		tkl_expire_at = config_checkval(str, CFG_TIME);
		if (tkl_expire_at > 0)
			tkl_expire_at = TStime() + tkl_expire_at;
	} else
	if ((str = json_object_get_string(params, "expire_at")))
	{
		tkl_expire_at = server_time_to_unix_time(str);
	} else
	{
		/* Never expire */
		tkl_expire_at = 0;
	}

	OPTIONAL_PARAM_STRING("set_by", set_by);
	if (!set_by)
		set_by = client->name;

	if ((tkl_expire_at != 0) && (tkl_expire_at < TStime()))
	{
		rpc_error_fmt(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Error: the specified expiry time is before current time (before now)");
		return;
	}

	if (find_tkl_serverban(tkl_type_int, usermask, hostmask, soft))
	{
		rpc_error(client, request, JSON_RPC_ERROR_ALREADY_EXISTS, "A ban with that mask already exists");
		return;
	}

	tkl = tkl_add_serverban(tkl_type_int, usermask, hostmask, NULL, reason,
	                        set_by, tkl_expire_at, tkl_set_at,
	                        soft, 0);

	if (!tkl)
	{
		rpc_error(client, request, JSON_RPC_ERROR_INTERNAL_ERROR, "Unable to add item");
		return;
	}

	tkl_added(client, tkl);

	result = json_object();
	json_expand_tkl(result, "tkl", tkl, 1);
	rpc_response(client, request, result);
	json_decref(result);
}
