pissircd/src/modules/targetfloodprot.c

324 lines
9.6 KiB
C

/* Target flood protection
* (C)Copyright 2020 Bram Matthys and the UnrealIRCd team
* License: GPLv2 or later
*/
#include "unrealircd.h"
ModuleHeader MOD_HEADER
= {
"targetfloodprot",
"5.0",
"Target flood protection (set::anti-flood::target-flood)",
"UnrealIRCd Team",
"unrealircd-6",
};
#define TFP_PRIVMSG 0
#define TFP_NOTICE 1
#define TFP_TAGMSG 2
#define TFP_MAX 3
typedef struct TargetFlood TargetFlood;
struct TargetFlood {
unsigned short cnt[TFP_MAX];
time_t t[TFP_MAX];
};
typedef struct TargetFloodConfig TargetFloodConfig;
struct TargetFloodConfig {
int cnt[TFP_MAX];
int t[TFP_MAX];
};
/* Forward declarations */
int targetfloodprot_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
int targetfloodprot_config_run(ConfigFile *cf, ConfigEntry *ce, int type);
void targetfloodprot_mdata_free(ModData *m);
int targetfloodprot_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype);
int targetfloodprot_can_send_to_user(Client *client, Client *target, const char **text, const char **errmsg, SendType sendtype);
/* Global variables */
ModDataInfo *targetfloodprot_client_md = NULL;
ModDataInfo *targetfloodprot_channel_md = NULL;
TargetFloodConfig *channelcfg = NULL;
TargetFloodConfig *privatecfg = NULL;
MOD_TEST()
{
HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, targetfloodprot_config_test);
return MOD_SUCCESS;
}
/** Allocate config and set default configuration */
void targetfloodprot_defaults(void)
{
channelcfg = safe_alloc(sizeof(TargetFloodConfig));
privatecfg = safe_alloc(sizeof(TargetFloodConfig));
/* set::anti-flood::target-flood::channel-privmsg */
channelcfg->cnt[TFP_PRIVMSG] = 45;
channelcfg->t[TFP_PRIVMSG] = 5;
/* set::anti-flood::target-flood::channel-notice */
channelcfg->cnt[TFP_NOTICE] = 15;
channelcfg->t[TFP_NOTICE] = 5;
/* set::anti-flood::target-flood::channel-tagmsg */
channelcfg->cnt[TFP_TAGMSG] = 15;
channelcfg->t[TFP_TAGMSG] = 5;
/* set::anti-flood::target-flood::private-privmsg */
privatecfg->cnt[TFP_PRIVMSG] = 30;
privatecfg->t[TFP_PRIVMSG] = 5;
/* set::anti-flood::target-flood::private-notice */
privatecfg->cnt[TFP_NOTICE] = 10;
privatecfg->t[TFP_NOTICE] = 5;
/* set::anti-flood::target-flood::private-tagmsg */
privatecfg->cnt[TFP_TAGMSG] = 10;
privatecfg->t[TFP_TAGMSG] = 5;
}
MOD_INIT()
{
ModDataInfo mreq;
MARK_AS_OFFICIAL_MODULE(modinfo);
HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, targetfloodprot_config_run);
HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_CHANNEL, 0, targetfloodprot_can_send_to_channel);
HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_USER, 0, targetfloodprot_can_send_to_user);
memset(&mreq, 0, sizeof(mreq));
mreq.name = "targetfloodprot";
mreq.serialize = NULL;
mreq.unserialize = NULL;
mreq.free = targetfloodprot_mdata_free;
mreq.sync = 0;
mreq.type = MODDATATYPE_LOCAL_CLIENT;
targetfloodprot_client_md = ModDataAdd(modinfo->handle, mreq);
memset(&mreq, 0, sizeof(mreq));
mreq.name = "targetfloodprot";
mreq.serialize = NULL;
mreq.unserialize = NULL;
mreq.free = targetfloodprot_mdata_free;
mreq.sync = 0;
mreq.type = MODDATATYPE_CHANNEL;
targetfloodprot_channel_md = ModDataAdd(modinfo->handle, mreq);
targetfloodprot_defaults();
return MOD_SUCCESS;
}
MOD_LOAD()
{
return MOD_SUCCESS;
}
MOD_UNLOAD()
{
safe_free(channelcfg);
safe_free(privatecfg);
return MOD_SUCCESS;
}
int targetfloodprot_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
{
int errors = 0;
ConfigEntry *cep;
if (type != CONFIG_SET_ANTI_FLOOD)
return 0;
/* We are only interrested in set::anti-flood::target-flood.. */
if (!ce || !ce->name || strcmp(ce->name, "target-flood"))
return 0;
for (cep = ce->items; cep; cep = cep->next)
{
CheckNull(cep);
if (!strcmp(cep->name, "channel-privmsg") ||
!strcmp(cep->name, "channel-notice") ||
!strcmp(cep->name, "channel-tagmsg") ||
!strcmp(cep->name, "private-privmsg") ||
!strcmp(cep->name, "private-notice") ||
!strcmp(cep->name, "private-tagmsg"))
{
int cnt = 0, period = 0;
if (!config_parse_flood(cep->value, &cnt, &period) ||
(cnt < 1) || (cnt > 10000) || (period < 1) || (period > 120))
{
config_error("%s:%i: set::anti-flood::target-flood::%s error. "
"Syntax is '<count>:<period>' (eg 5:60). "
"Count must be 1-10000 and period must be 1-120.",
cep->file->filename, cep->line_number,
cep->name);
errors++;
}
} else
{
config_error("%s:%i: unknown directive set::anti-flood::target-flood:%s",
cep->file->filename, cep->line_number, cep->name);
errors++;
continue;
}
}
*errs = errors;
return errors ? -1 : 1;
}
int targetfloodprot_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
{
ConfigEntry *cep, *cepp;
if (type != CONFIG_SET_ANTI_FLOOD)
return 0;
/* We are only interrested in set::anti-flood::target-flood.. */
if (!ce || !ce->name || strcmp(ce->name, "target-flood"))
return 0;
for (cep = ce->items; cep; cep = cep->next)
{
if (!strcmp(cep->name, "channel-privmsg"))
config_parse_flood(cep->value, &channelcfg->cnt[TFP_PRIVMSG], &channelcfg->t[TFP_PRIVMSG]);
else if (!strcmp(cep->name, "channel-notice"))
config_parse_flood(cep->value, &channelcfg->cnt[TFP_NOTICE], &channelcfg->t[TFP_NOTICE]);
else if (!strcmp(cep->name, "channel-tagmsg"))
config_parse_flood(cep->value, &channelcfg->cnt[TFP_TAGMSG], &channelcfg->t[TFP_TAGMSG]);
else if (!strcmp(cep->name, "private-privmsg"))
config_parse_flood(cep->value, &privatecfg->cnt[TFP_PRIVMSG], &privatecfg->t[TFP_PRIVMSG]);
else if (!strcmp(cep->name, "private-notice"))
config_parse_flood(cep->value, &privatecfg->cnt[TFP_NOTICE], &privatecfg->t[TFP_NOTICE]);
else if (!strcmp(cep->name, "private-tagmsg"))
config_parse_flood(cep->value, &privatecfg->cnt[TFP_TAGMSG], &privatecfg->t[TFP_TAGMSG]);
}
return 1;
}
/** UnrealIRCd internals: free object. */
void targetfloodprot_mdata_free(ModData *m)
{
/* we don't have any members to free, so this is easy */
safe_free(m->ptr);
}
int sendtypetowhat(SendType sendtype)
{
if (sendtype == SEND_TYPE_PRIVMSG)
return 0;
if (sendtype == SEND_TYPE_NOTICE)
return 1;
if (sendtype == SEND_TYPE_TAGMSG)
return 2;
#ifdef DEBUGMODE
unreal_log(ULOG_ERROR, "flood", "BUG_SENDTYPETOWHAT_UNKNOWN_VALUE", NULL,
"[BUG] sendtypetowhat() called for unknown sendtype $send_type",
log_data_integer("send_type", sendtype));
abort();
#endif
return 0; /* otherwise, default to privmsg i guess */
}
int targetfloodprot_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype)
{
TargetFlood *flood;
static char errbuf[256];
int what;
/* This is redundant, right? */
if (!MyUser(client))
return HOOK_CONTINUE;
/* U-Lines, servers and IRCOps override */
if (IsULine(client) || !IsUser(client) || (IsOper(client) && ValidatePermissionsForPath("immune:target-flood",client,NULL,channel,NULL)))
return HOOK_CONTINUE;
what = sendtypetowhat(sendtype);
if (moddata_channel(channel, targetfloodprot_channel_md).ptr == NULL)
{
/* Alloc a new entry if it doesn't exist yet */
moddata_channel(channel, targetfloodprot_channel_md).ptr = safe_alloc(sizeof(TargetFlood));
}
flood = (TargetFlood *)moddata_channel(channel, targetfloodprot_channel_md).ptr;
if ((TStime() - flood->t[what]) >= channelcfg->t[what])
{
/* Reset due to moving into a new time slot */
flood->t[what] = TStime();
flood->cnt[what] = 1;
return HOOK_CONTINUE; /* forget about it.. */
}
if (flood->cnt[what] >= channelcfg->cnt[what])
{
/* Flood detected */
unreal_log(ULOG_INFO, "flood", "FLOOD_BLOCKED", client,
"Flood blocked ($flood_type) from $client.details [$client.ip] to $channel",
log_data_string("flood_type", "target-flood-channel"),
log_data_channel("channel", channel));
snprintf(errbuf, sizeof(errbuf), "Channel is being flooded. Message not delivered.");
*errmsg = errbuf;
return HOOK_DENY;
}
flood->cnt[what]++;
return HOOK_CONTINUE;
}
int targetfloodprot_can_send_to_user(Client *client, Client *target, const char **text, const char **errmsg, SendType sendtype)
{
TargetFlood *flood;
static char errbuf[256];
int what;
/* Check if it is our TARGET ('target'), so yeah
* be aware that 'client' may be remote client in all the code that follows!
*/
if (!MyUser(target))
return HOOK_CONTINUE;
/* U-Lines, servers and IRCOps override */
if (IsULine(client) || !IsUser(client) || (IsOper(client) && ValidatePermissionsForPath("immune:target-flood",client,target,NULL,NULL)))
return HOOK_CONTINUE;
what = sendtypetowhat(sendtype);
if (moddata_local_client(target, targetfloodprot_client_md).ptr == NULL)
{
/* Alloc a new entry if it doesn't exist yet */
moddata_local_client(target, targetfloodprot_client_md).ptr = safe_alloc(sizeof(TargetFlood));
}
flood = (TargetFlood *)moddata_local_client(target, targetfloodprot_client_md).ptr;
if ((TStime() - flood->t[what]) >= privatecfg->t[what])
{
/* Reset due to moving into a new time slot */
flood->t[what] = TStime();
flood->cnt[what] = 1;
return HOOK_CONTINUE; /* forget about it.. */
}
if (flood->cnt[what] >= privatecfg->cnt[what])
{
/* Flood detected */
unreal_log(ULOG_INFO, "flood", "FLOOD_BLOCKED", client,
"Flood blocked ($flood_type) from $client.details [$client.ip] to $target",
log_data_string("flood_type", "target-flood-user"),
log_data_client("target", target));
snprintf(errbuf, sizeof(errbuf), "User is being flooded. Message not delivered.");
*errmsg = errbuf;
return HOOK_DENY;
}
flood->cnt[what]++;
return HOOK_CONTINUE;
}