mirror of https://github.com/pissnet/pissircd.git
324 lines
9.6 KiB
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;
|
|
}
|