unrealircd

- supernets unrealircd source & configuration
git clone git://git.acid.vegas/unrealircd.git
Log | Files | Refs | Archive | README | LICENSE

targetfloodprot.c (9821B)

      1 /* Target flood protection
      2  * (C)Copyright 2020 Bram Matthys and the UnrealIRCd team
      3  * License: GPLv2 or later
      4  */
      5    
      6 #include "unrealircd.h"
      7 
      8 ModuleHeader MOD_HEADER
      9   = {
     10 	"targetfloodprot",
     11 	"5.0",
     12 	"Target flood protection (set::anti-flood::target-flood)",
     13 	"UnrealIRCd Team",
     14 	"unrealircd-6",
     15     };
     16 
     17 #define TFP_PRIVMSG	0
     18 #define TFP_NOTICE	1
     19 #define TFP_TAGMSG	2
     20 #define TFP_MAX		3
     21 
     22 typedef struct TargetFlood TargetFlood;
     23 struct TargetFlood {
     24 	unsigned short cnt[TFP_MAX];
     25 	time_t t[TFP_MAX];
     26 };
     27 
     28 typedef struct TargetFloodConfig TargetFloodConfig;
     29 struct TargetFloodConfig {
     30 	int cnt[TFP_MAX];
     31 	int t[TFP_MAX];
     32 };
     33 
     34 /* Forward declarations */
     35 int targetfloodprot_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
     36 int targetfloodprot_config_run(ConfigFile *cf, ConfigEntry *ce, int type);
     37 void targetfloodprot_mdata_free(ModData *m);
     38 int targetfloodprot_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype);
     39 int targetfloodprot_can_send_to_user(Client *client, Client *target, const char **text, const char **errmsg, SendType sendtype);
     40 
     41 /* Global variables */
     42 ModDataInfo *targetfloodprot_client_md = NULL;
     43 ModDataInfo *targetfloodprot_channel_md = NULL;
     44 TargetFloodConfig *channelcfg = NULL;
     45 TargetFloodConfig *privatecfg = NULL;
     46 
     47 MOD_TEST()
     48 {
     49 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, targetfloodprot_config_test);
     50 	return MOD_SUCCESS;
     51 }
     52 
     53 /** Allocate config and set default configuration */
     54 void targetfloodprot_defaults(void)
     55 {
     56 	channelcfg = safe_alloc(sizeof(TargetFloodConfig));
     57 	privatecfg = safe_alloc(sizeof(TargetFloodConfig));
     58 
     59 	/* set::anti-flood::target-flood::channel-privmsg */
     60 	channelcfg->cnt[TFP_PRIVMSG] = 45;
     61 	channelcfg->t[TFP_PRIVMSG] = 5;
     62 	/* set::anti-flood::target-flood::channel-notice */
     63 	channelcfg->cnt[TFP_NOTICE] = 15;
     64 	channelcfg->t[TFP_NOTICE] = 5;
     65 	/* set::anti-flood::target-flood::channel-tagmsg */
     66 	channelcfg->cnt[TFP_TAGMSG] = 15;
     67 	channelcfg->t[TFP_TAGMSG] = 5;
     68 
     69 	/* set::anti-flood::target-flood::private-privmsg */
     70 	privatecfg->cnt[TFP_PRIVMSG] = 30;
     71 	privatecfg->t[TFP_PRIVMSG] = 5;
     72 	/* set::anti-flood::target-flood::private-notice */
     73 	privatecfg->cnt[TFP_NOTICE] = 10;
     74 	privatecfg->t[TFP_NOTICE] = 5;
     75 	/* set::anti-flood::target-flood::private-tagmsg */
     76 	privatecfg->cnt[TFP_TAGMSG] = 10;
     77 	privatecfg->t[TFP_TAGMSG] = 5;
     78 }
     79 
     80 MOD_INIT()
     81 {
     82 	ModDataInfo mreq;
     83 
     84 	MARK_AS_OFFICIAL_MODULE(modinfo);
     85 
     86 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, targetfloodprot_config_run);
     87 	HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_CHANNEL, 0, targetfloodprot_can_send_to_channel);
     88 	HookAdd(modinfo->handle, HOOKTYPE_CAN_SEND_TO_USER, 0, targetfloodprot_can_send_to_user);
     89 
     90 	memset(&mreq, 0, sizeof(mreq));
     91 	mreq.name = "targetfloodprot";
     92 	mreq.serialize = NULL;
     93 	mreq.unserialize = NULL;
     94 	mreq.free = targetfloodprot_mdata_free;
     95 	mreq.sync = 0;
     96 	mreq.type = MODDATATYPE_LOCAL_CLIENT;
     97 	targetfloodprot_client_md = ModDataAdd(modinfo->handle, mreq);
     98 
     99 	memset(&mreq, 0, sizeof(mreq));
    100 	mreq.name = "targetfloodprot";
    101 	mreq.serialize = NULL;
    102 	mreq.unserialize = NULL;
    103 	mreq.free = targetfloodprot_mdata_free;
    104 	mreq.sync = 0;
    105 	mreq.type = MODDATATYPE_CHANNEL;
    106 	targetfloodprot_channel_md = ModDataAdd(modinfo->handle, mreq);
    107 
    108 	targetfloodprot_defaults();
    109 
    110 	return MOD_SUCCESS;
    111 }
    112 
    113 MOD_LOAD()
    114 {
    115 	return MOD_SUCCESS;
    116 }
    117 
    118 MOD_UNLOAD()
    119 {
    120 	safe_free(channelcfg);
    121 	safe_free(privatecfg);
    122 	return MOD_SUCCESS;
    123 }
    124 
    125 int targetfloodprot_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
    126 {
    127 	int errors = 0;
    128 	ConfigEntry *cep;
    129 
    130 	if (type != CONFIG_SET_ANTI_FLOOD)
    131 		return 0;
    132 
    133 	/* We are only interrested in set::anti-flood::target-flood.. */
    134 	if (!ce || !ce->name || strcmp(ce->name, "target-flood"))
    135 		return 0;
    136 
    137 	for (cep = ce->items; cep; cep = cep->next)
    138 	{
    139 		CheckNull(cep);
    140 
    141 		if (!strcmp(cep->name, "channel-privmsg") ||
    142 		    !strcmp(cep->name, "channel-notice") ||
    143 		    !strcmp(cep->name, "channel-tagmsg") ||
    144 		    !strcmp(cep->name, "private-privmsg") ||
    145 		    !strcmp(cep->name, "private-notice") ||
    146 		    !strcmp(cep->name, "private-tagmsg"))
    147 		{
    148 			int cnt = 0, period = 0;
    149 
    150 			if (!config_parse_flood(cep->value, &cnt, &period) ||
    151 			    (cnt < 1) || (cnt > 10000) || (period < 1) || (period > 120))
    152 			{
    153 				config_error("%s:%i: set::anti-flood::target-flood::%s error. "
    154 				             "Syntax is '<count>:<period>' (eg 5:60). "
    155 				             "Count must be 1-10000 and period must be 1-120.",
    156 				             cep->file->filename, cep->line_number,
    157 				             cep->name);
    158 				errors++;
    159 			}
    160 		} else
    161 		{
    162 			config_error("%s:%i: unknown directive set::anti-flood::target-flood:%s",
    163 				cep->file->filename, cep->line_number, cep->name);
    164 			errors++;
    165 			continue;
    166 		}
    167 	}
    168 
    169 	*errs = errors;
    170 	return errors ? -1 : 1;
    171 }
    172 
    173 int targetfloodprot_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
    174 {
    175 	ConfigEntry *cep, *cepp;
    176 
    177 	if (type != CONFIG_SET_ANTI_FLOOD)
    178 		return 0;
    179 
    180 	/* We are only interrested in set::anti-flood::target-flood.. */
    181 	if (!ce || !ce->name || strcmp(ce->name, "target-flood"))
    182 		return 0;
    183 
    184 	for (cep = ce->items; cep; cep = cep->next)
    185 	{
    186 		if (!strcmp(cep->name, "channel-privmsg"))
    187 			config_parse_flood(cep->value, &channelcfg->cnt[TFP_PRIVMSG], &channelcfg->t[TFP_PRIVMSG]);
    188 		else if (!strcmp(cep->name, "channel-notice"))
    189 			config_parse_flood(cep->value, &channelcfg->cnt[TFP_NOTICE], &channelcfg->t[TFP_NOTICE]);
    190 		else if (!strcmp(cep->name, "channel-tagmsg"))
    191 			config_parse_flood(cep->value, &channelcfg->cnt[TFP_TAGMSG], &channelcfg->t[TFP_TAGMSG]);
    192 		else if (!strcmp(cep->name, "private-privmsg"))
    193 			config_parse_flood(cep->value, &privatecfg->cnt[TFP_PRIVMSG], &privatecfg->t[TFP_PRIVMSG]);
    194 		else if (!strcmp(cep->name, "private-notice"))
    195 			config_parse_flood(cep->value, &privatecfg->cnt[TFP_NOTICE], &privatecfg->t[TFP_NOTICE]);
    196 		else if (!strcmp(cep->name, "private-tagmsg"))
    197 			config_parse_flood(cep->value, &privatecfg->cnt[TFP_TAGMSG], &privatecfg->t[TFP_TAGMSG]);
    198 	}
    199 
    200 	return 1;
    201 }
    202 
    203 /** UnrealIRCd internals: free object. */
    204 void targetfloodprot_mdata_free(ModData *m)
    205 {
    206 	/* we don't have any members to free, so this is easy */
    207 	safe_free(m->ptr);
    208 }
    209 
    210 int sendtypetowhat(SendType sendtype)
    211 {
    212 	if (sendtype == SEND_TYPE_PRIVMSG)
    213 		return 0;
    214 	if (sendtype == SEND_TYPE_NOTICE)
    215 		return 1;
    216 	if (sendtype == SEND_TYPE_TAGMSG)
    217 		return 2;
    218 #ifdef DEBUGMODE
    219 	unreal_log(ULOG_ERROR, "flood", "BUG_SENDTYPETOWHAT_UNKNOWN_VALUE", NULL,
    220 	           "[BUG] sendtypetowhat() called for unknown sendtype $send_type",
    221 	           log_data_integer("send_type", sendtype));
    222 	abort();
    223 #endif
    224 	return 0; /* otherwise, default to privmsg i guess */
    225 }
    226 
    227 int targetfloodprot_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype)
    228 {
    229 	TargetFlood *flood;
    230 	static char errbuf[256];
    231 	int what;
    232 
    233 	/* This is redundant, right? */
    234 	if (!MyUser(client))
    235 		return HOOK_CONTINUE;
    236 
    237 	/* U-Lines, servers and IRCOps override */
    238 	if (IsULine(client) || !IsUser(client) || (IsOper(client) && ValidatePermissionsForPath("immune:target-flood",client,NULL,channel,NULL)))
    239 		return HOOK_CONTINUE;
    240 
    241 	what = sendtypetowhat(sendtype);
    242 
    243 	if (moddata_channel(channel, targetfloodprot_channel_md).ptr == NULL)
    244 	{
    245 		/* Alloc a new entry if it doesn't exist yet */
    246 		moddata_channel(channel, targetfloodprot_channel_md).ptr = safe_alloc(sizeof(TargetFlood));
    247 	}
    248 
    249 	flood = (TargetFlood *)moddata_channel(channel, targetfloodprot_channel_md).ptr;
    250 
    251 	if ((TStime() - flood->t[what]) >= channelcfg->t[what])
    252 	{
    253 		/* Reset due to moving into a new time slot */
    254 		flood->t[what] = TStime();
    255 		flood->cnt[what] = 1;
    256 		return HOOK_CONTINUE; /* forget about it.. */
    257 	}
    258 
    259 	if (flood->cnt[what] >= channelcfg->cnt[what])
    260 	{
    261 		/* Flood detected */
    262 		unreal_log(ULOG_INFO, "flood", "FLOOD_BLOCKED", client,
    263 			   "Flood blocked ($flood_type) from $client.details [$client.ip] to $channel",
    264 			   log_data_string("flood_type", "target-flood-channel"),
    265 			   log_data_channel("channel", channel));
    266 		snprintf(errbuf, sizeof(errbuf), "Channel is being flooded. Message not delivered.");
    267 		*errmsg = errbuf;
    268 		return HOOK_DENY;
    269 	}
    270 
    271 	flood->cnt[what]++;
    272 	return HOOK_CONTINUE;
    273 }
    274 
    275 int targetfloodprot_can_send_to_user(Client *client, Client *target, const char **text, const char **errmsg, SendType sendtype)
    276 {
    277 	TargetFlood *flood;
    278 	static char errbuf[256];
    279 	int what;
    280 
    281 	/* Check if it is our TARGET ('target'), so yeah
    282 	 * be aware that 'client' may be remote client in all the code that follows!
    283 	 */
    284 	if (!MyUser(target))
    285 		return HOOK_CONTINUE;
    286 
    287 	/* U-Lines, servers and IRCOps override */
    288 	if (IsULine(client) || !IsUser(client) || (IsOper(client) && ValidatePermissionsForPath("immune:target-flood",client,target,NULL,NULL)))
    289 		return HOOK_CONTINUE;
    290 
    291 	what = sendtypetowhat(sendtype);
    292 
    293 	if (moddata_local_client(target, targetfloodprot_client_md).ptr == NULL)
    294 	{
    295 		/* Alloc a new entry if it doesn't exist yet */
    296 		moddata_local_client(target, targetfloodprot_client_md).ptr = safe_alloc(sizeof(TargetFlood));
    297 	}
    298 
    299 	flood = (TargetFlood *)moddata_local_client(target, targetfloodprot_client_md).ptr;
    300 
    301 	if ((TStime() - flood->t[what]) >= privatecfg->t[what])
    302 	{
    303 		/* Reset due to moving into a new time slot */
    304 		flood->t[what] = TStime();
    305 		flood->cnt[what] = 1;
    306 		return HOOK_CONTINUE; /* forget about it.. */
    307 	}
    308 
    309 	if (flood->cnt[what] >= privatecfg->cnt[what])
    310 	{
    311 		/* Flood detected */
    312 		unreal_log(ULOG_INFO, "flood", "FLOOD_BLOCKED", client,
    313 			   "Flood blocked ($flood_type) from $client.details [$client.ip] to $target",
    314 			   log_data_string("flood_type", "target-flood-user"),
    315 			   log_data_client("target", target));
    316 		snprintf(errbuf, sizeof(errbuf), "User is being flooded. Message not delivered.");
    317 		*errmsg = errbuf;
    318 		return HOOK_DENY;
    319 	}
    320 
    321 	flood->cnt[what]++;
    322 	return HOOK_CONTINUE;
    323 }