mirror of git://git.acid.vegas/unrealircd.git
316 lines
9.2 KiB
C
316 lines
9.2 KiB
C
/* Target flood protection
|
|
* (C)Copyright 2020 Bram Matthys and the UnrealIRCd team
|
|
* License: GPLv2
|
|
*/
|
|
|
|
#include "unrealircd.h"
|
|
|
|
ModuleHeader MOD_HEADER
|
|
= {
|
|
"targetfloodprot",
|
|
"5.0",
|
|
"Target flood protection (set::anti-flood::target-flood)",
|
|
"UnrealIRCd Team",
|
|
"unrealircd-5",
|
|
};
|
|
|
|
#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, char **msg, char **errmsg, SendType sendtype);
|
|
int targetfloodprot_can_send_to_user(Client *client, Client *target, char **text, 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()
|
|
{
|
|
return MOD_SUCCESS;
|
|
}
|
|
|
|
#ifndef CheckNull
|
|
#define CheckNull(x) if ((!(x)->ce_vardata) || (!(*((x)->ce_vardata)))) { config_error("%s:%i: missing parameter", (x)->ce_fileptr->cf_filename, (x)->ce_varlinenum); errors++; continue; }
|
|
#endif
|
|
|
|
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->ce_varname || strcmp(ce->ce_varname, "target-flood"))
|
|
return 0;
|
|
|
|
for (cep = ce->ce_entries; cep; cep = cep->ce_next)
|
|
{
|
|
CheckNull(cep);
|
|
|
|
if (!strcmp(cep->ce_varname, "channel-privmsg") ||
|
|
!strcmp(cep->ce_varname, "channel-notice") ||
|
|
!strcmp(cep->ce_varname, "channel-tagmsg") ||
|
|
!strcmp(cep->ce_varname, "private-privmsg") ||
|
|
!strcmp(cep->ce_varname, "private-notice") ||
|
|
!strcmp(cep->ce_varname, "private-tagmsg"))
|
|
{
|
|
int cnt = 0, period = 0;
|
|
|
|
if (!config_parse_flood(cep->ce_vardata, &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->ce_fileptr->cf_filename, cep->ce_varlinenum,
|
|
cep->ce_varname);
|
|
errors++;
|
|
}
|
|
} else
|
|
{
|
|
config_error("%s:%i: unknown directive set::anti-flood::target-flood:%s",
|
|
cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname);
|
|
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->ce_varname || strcmp(ce->ce_varname, "target-flood"))
|
|
return 0;
|
|
|
|
for (cep = ce->ce_entries; cep; cep = cep->ce_next)
|
|
{
|
|
if (!strcmp(cep->ce_varname, "channel-privmsg"))
|
|
config_parse_flood(cep->ce_vardata, &channelcfg->cnt[TFP_PRIVMSG], &channelcfg->t[TFP_PRIVMSG]);
|
|
else if (!strcmp(cep->ce_varname, "channel-notice"))
|
|
config_parse_flood(cep->ce_vardata, &channelcfg->cnt[TFP_NOTICE], &channelcfg->t[TFP_NOTICE]);
|
|
else if (!strcmp(cep->ce_varname, "channel-tagmsg"))
|
|
config_parse_flood(cep->ce_vardata, &channelcfg->cnt[TFP_TAGMSG], &channelcfg->t[TFP_TAGMSG]);
|
|
else if (!strcmp(cep->ce_varname, "private-privmsg"))
|
|
config_parse_flood(cep->ce_vardata, &privatecfg->cnt[TFP_PRIVMSG], &privatecfg->t[TFP_PRIVMSG]);
|
|
else if (!strcmp(cep->ce_varname, "private-notice"))
|
|
config_parse_flood(cep->ce_vardata, &privatecfg->cnt[TFP_NOTICE], &privatecfg->t[TFP_NOTICE]);
|
|
else if (!strcmp(cep->ce_varname, "private-tagmsg"))
|
|
config_parse_flood(cep->ce_vardata, &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
|
|
ircd_log(LOG_ERROR, "sendtypetowhat() for unknown value %d", (int)sendtype);
|
|
abort();
|
|
#endif
|
|
return 0; /* otherwise, default to privmsg i guess */
|
|
}
|
|
|
|
int targetfloodprot_can_send_to_channel(Client *client, Channel *channel, Membership *lp, char **msg, char **errmsg, SendType sendtype)
|
|
{
|
|
TargetFlood *flood;
|
|
static char errbuf[256];
|
|
int what;
|
|
|
|
/* This is redundant, right? */
|
|
if (!MyUser(client))
|
|
return HOOK_CONTINUE;
|
|
|
|
/* IRCOps and U-Lines override */
|
|
if (IsULine(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 */
|
|
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, char **text, 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;
|
|
|
|
/* IRCOps and U-Lines override */
|
|
if (IsULine(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 */
|
|
snprintf(errbuf, sizeof(errbuf), "User is being flooded. Message not delivered.");
|
|
*errmsg = errbuf;
|
|
return HOOK_DENY;
|
|
}
|
|
|
|
flood->cnt[what]++;
|
|
return HOOK_CONTINUE;
|
|
}
|