pissircd/src/modules/rpc/spamfilter.c

334 lines
8.3 KiB
C

/* spamfilter.* RPC calls
* (C) Copyright 2022-.. Bram Matthys (Syzop) and the UnrealIRCd team
* License: GPLv2 or later
*/
#include "unrealircd.h"
ModuleHeader MOD_HEADER
= {
"rpc/spamfilter",
"1.0.3",
"spamfilter.* RPC calls",
"UnrealIRCd Team",
"unrealircd-6",
};
/* Forward declarations */
RPC_CALL_FUNC(rpc_spamfilter_list);
RPC_CALL_FUNC(rpc_spamfilter_get);
RPC_CALL_FUNC(rpc_spamfilter_del);
RPC_CALL_FUNC(rpc_spamfilter_add);
MOD_INIT()
{
RPCHandlerInfo r;
MARK_AS_OFFICIAL_MODULE(modinfo);
memset(&r, 0, sizeof(r));
r.method = "spamfilter.list";
r.loglevel = ULOG_DEBUG;
r.call = rpc_spamfilter_list;
if (!RPCHandlerAdd(modinfo->handle, &r))
{
config_error("[rpc/spamfilter] Could not register RPC handler");
return MOD_FAILED;
}
r.method = "spamfilter.get";
r.loglevel = ULOG_DEBUG;
r.call = rpc_spamfilter_get;
if (!RPCHandlerAdd(modinfo->handle, &r))
{
config_error("[rpc/spamfilter] Could not register RPC handler");
return MOD_FAILED;
}
r.method = "spamfilter.del";
r.call = rpc_spamfilter_del;
if (!RPCHandlerAdd(modinfo->handle, &r))
{
config_error("[rpc/spamfilter] Could not register RPC handler");
return MOD_FAILED;
}
r.method = "spamfilter.add";
r.call = rpc_spamfilter_add;
if (!RPCHandlerAdd(modinfo->handle, &r))
{
config_error("[rpc/spamfilter] 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_spamfilter_list)
{
json_t *result, *list, *item;
int index;
TKL *tkl;
result = json_object();
list = json_array();
json_object_set_new(result, "list", list);
for (index = 0; index < TKLISTLEN; index++)
{
for (tkl = tklines[index]; tkl; tkl = tkl->next)
{
if (TKLIsSpamfilter(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 spamfilter, for .add/.del/get */
int spamfilter_select_criteria(Client *client, json_t *request, json_t *params, const char **name, int *match_type,
int *targets, char *targetbuf, size_t targetbuflen, BanActionValue *action, char *actionbuf)
{
const char *str;
int actval;
*name = json_object_get_string(params, "name");
if (!*name)
{
rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: 'name'");
return 0;
}
str = json_object_get_string(params, "match_type");
if (!str)
{
rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: 'match_type'");
return 0;
}
*match_type = unreal_match_method_strtoval(str);
if (!*match_type)
{
rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Invalid value for parameter 'match_type'");
return 0;
}
str = json_object_get_string(params, "spamfilter_targets");
if (!str)
{
rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: 'spamfilter_targets'");
return 0;
}
*targets = spamfilter_gettargets(str, NULL);
if (!*targets)
{
rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Invalid value(s) for parameter 'spamfilter_targets'");
return 0;
}
strlcpy(targetbuf, spamfilter_target_inttostring(*targets), targetbuflen);
str = json_object_get_string(params, "ban_action");
if (!str)
{
rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: 'ban_action'");
return 0;
}
*action = banact_stringtoval(str);
if (!*action)
{
rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Invalid value for parameter 'ban_action'");
return 0;
}
/* I hope to remove this restriction at a later point: */
if (banact_config_only(*action))
{
rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Invalid value for parameter 'ban_action': action is config-based only");
return 0;
}
actionbuf[0] = banact_valtochar(*action);
actionbuf[1] = '\0';
return 1;
}
RPC_CALL_FUNC(rpc_spamfilter_get)
{
json_t *result;
int type = TKL_SPAMF|TKL_GLOBAL;
const char *name;
TKL *tkl;
BanActionValue action;
int match_type = 0;
int targets = 0;
char targetbuf[64];
char actionbuf[2];
if (!spamfilter_select_criteria(client, request, params, &name, &match_type, &targets, targetbuf, sizeof(targetbuf), &action, actionbuf))
return; /* Error already communicated to client */
tkl = find_tkl_spamfilter(type, name, action, targets);
if (!tkl)
{
rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Spamfilter 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_spamfilter_add)
{
json_t *result;
int type = TKL_SPAMF|TKL_GLOBAL;
const char *str;
const char *name, *reason;
const char *set_by;
time_t ban_duration = 0;
TKL *tkl;
Match *m;
BanActionValue action;
int match_type = 0;
int targets = 0;
char targetbuf[64];
char actionbuf[2];
char reasonbuf[512];
char *err = NULL;
if (!spamfilter_select_criteria(client, request, params, &name, &match_type, &targets, targetbuf, sizeof(targetbuf), &action, actionbuf))
return; /* Error already communicated to client */
/* Reason */
reason = json_object_get_string(params, "reason");
if (!reason)
{
rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Missing parameter: 'reason'");
return;
}
/* Ban duration */
if ((str = json_object_get_string(params, "ban_duration")))
{
ban_duration = config_checkval(str, CFG_TIME);
if (ban_duration < 0)
{
rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Invalid value for parameter 'ban_duration'");
return;
}
}
OPTIONAL_PARAM_STRING("set_by", set_by);
if (!set_by)
set_by = client->name;
if (find_tkl_spamfilter(type, name, action, targets))
{
rpc_error(client, request, JSON_RPC_ERROR_ALREADY_EXISTS, "A spamfilter with that regex+action+target already exists");
return;
}
/* Convert reason to use internal storage and wire format */
reason = unreal_encodespace(reason);
strlcpy(reasonbuf, reason, sizeof(reasonbuf));
reason = reasonbuf;
/* now check the regex / match field (only when adding) */
m = unreal_create_match(match_type, name, &err);
if (!m)
{
rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Invalid regex or match string specified");
return;
}
tkl = tkl_add_spamfilter(type, NULL, targets, banact_value_to_struct(action), m, NULL, NULL, set_by, 0, TStime(),
ban_duration, reason, 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);
}
RPC_CALL_FUNC(rpc_spamfilter_del)
{
json_t *result;
int type = TKL_SPAMF|TKL_GLOBAL;
const char *name;
const char *set_by;
TKL *tkl;
BanActionValue action;
int match_type = 0;
int targets = 0;
char targetbuf[64];
char actionbuf[2];
const char *tkllayer[13];
if (!spamfilter_select_criteria(client, request, params, &name, &match_type, &targets, targetbuf, sizeof(targetbuf), &action, actionbuf))
return; /* Error already communicated to client */
OPTIONAL_PARAM_STRING("set_by", set_by);
if (!set_by)
set_by = client->name;
tkl = find_tkl_spamfilter(type, name, action, targets);
if (!tkl)
{
rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Spamfilter not found");
return;
}
result = json_object();
json_expand_tkl(result, "tkl", tkl, 1);
/* Wait.. this is a bit dull? */
tkllayer[1] = "-";
tkllayer[2] = "F";
tkllayer[3] = targetbuf;
tkllayer[4] = actionbuf;
tkllayer[5] = set_by;
tkllayer[6] = "-";
tkllayer[7] = "0";
tkllayer[8] = "0";
tkllayer[9] = "-";
tkllayer[10] = unreal_match_method_valtostr(match_type);
tkllayer[11] = name;
tkllayer[12] = NULL;
cmd_tkl(&me, NULL, 12, tkllayer);
tkl = find_tkl_spamfilter(type, name, action, targets);
if (!tkl)
{
rpc_response(client, request, result);
} else {
/* Spamfilter still exists so failure to remove.
* Actually this may not be an internal error, it could be an
* incorrect request, such as asking to remove a config-based spamfilter.
*/
rpc_error(client, request, JSON_RPC_ERROR_INTERNAL_ERROR, "Unable to remove item");
return;
}
json_decref(result);
}