1 /*
2  * flood.c: handle channel flooding.
3  *
4  * This attempts to give you some protection from flooding.  Basically, it keeps
5  * track of how far apart (timewise) messages come in from different people.
6  * If a single nickname sends more than 3 messages in a row in under a
7  * second, this is considered flooding.  It then activates the ON FLOOD with
8  * the nickname and type (appropriate for use with IGNORE).
9  *
10  * Thanks to Tomi Ollila <f36664r@puukko.hut.fi> for this one.
11  */
12 
13 
14 #include "irc.h"
15 static char cvsrevision[] = "$Id: flood.c 443 2013-11-11 21:20:39Z tcava $";
16 CVS_REVISION(flood_c)
17 #include "struct.h"
18 
19 #include "alias.h"
20 #include "hook.h"
21 #include "ircaux.h"
22 #include "ignore.h"
23 #include "flood.h"
24 #include "vars.h"
25 #include "output.h"
26 #include "list.h"
27 #include "misc.h"
28 #include "server.h"
29 #include "userlist.h"
30 #include "timer.h"
31 #include "ignore.h"
32 #include "status.h"
33 #include "hash2.h"
34 #include "cset.h"
35 #define MAIN_SOURCE
36 #include "modval.h"
37 
38 static	char	*ignore_types[] =
39 {
40 	"",
41 	"MSG",
42 	"PUBLIC",
43 	"NOTICE",
44 	"WALL",
45 	"WALLOP",
46 	"CTCP",
47 	"INVITE",
48 	"CDCC",
49 	"ACTION",
50 	"NICK",
51 	"DEOP",
52 	"KICK",
53 	"JOIN"
54 };
55 
56 #define FLOOD_HASHSIZE 31
57 HashEntry no_flood_list[FLOOD_HASHSIZE];
58 HashEntry flood_list[FLOOD_HASHSIZE];
59 
60 static int remove_oldest_flood_hashlist(HashEntry *, time_t, int);
61 
62 
63 
64 
65 extern	char	*FromUserHost;
66 extern	unsigned int window_display;
67 extern	int	from_server;
68 
69 static double allow_flood = 0.0;
70 static double this_flood = 0.0;
71 
72 #define NO_RESET 0
73 #define RESET 1
74 
get_flood_types(unsigned int type)75 char *get_flood_types(unsigned int type)
76 {
77 int x = 0;
78 	while (type)
79 	{
80 		type = type >> 1;
81 		x++;
82 	}
83 	return ignore_types[x];
84 }
85 
86 #if 0
87 int get_flood_rate(int type, ChannelList * channel)
88 {
89 	int flood_rate = get_int_var(FLOOD_RATE_VAR);
90 	if (channel)
91 	{
92 		switch(type)
93 		{
94 			case JOIN_FLOOD:
95 				flood_rate = get_cset_int_var(channel->csets, JOINFLOOD_TIME_CSET);
96 				break;
97 			case PUBLIC_FLOOD:
98 				flood_rate = get_cset_int_var(channel->csets, PUBFLOOD_TIME_CSET);
99 				break;
100 			case NICK_FLOOD:
101 				flood_rate = get_cset_int_var(channel->csets, NICKFLOOD_TIME_CSET);
102 				break;
103 			case KICK_FLOOD:
104 				flood_rate = get_cset_int_var(channel->csets, KICKFLOOD_TIME_CSET);
105 				break;
106 			case DEOP_FLOOD:
107 				flood_rate = get_cset_int_var(channel->csets, DEOPFLOOD_TIME_CSET);
108 				break;
109 			default:
110 				break;
111 		}
112 	}
113 	else
114 	{
115 		switch(type)
116 		{
117 			case CDCC_FLOOD:
118 				flood_rate = get_int_var(CDCC_FLOOD_RATE_VAR);
119 				break;
120 			case CTCP_FLOOD:
121 				flood_rate = get_int_var(CTCP_FLOOD_RATE_VAR);
122 			case CTCP_ACTION_FLOOD:
123 			default:
124 				break;
125 		}
126 	}
127 	return flood_rate;
128 }
129 
130 int get_flood_count(int type, ChannelList * channel)
131 {
132 	int flood_count = get_int_var(FLOOD_AFTER_VAR);
133 	if (channel) {
134 		switch(type)
135 		{
136 			case JOIN_FLOOD:
137 				flood_count = get_cset_int_var(channel->csets, KICK_ON_JOINFLOOD_CSET);
138 				break;
139 			case PUBLIC_FLOOD:
140 				flood_count = get_cset_int_var(channel->csets, KICK_ON_PUBFLOOD_CSET);
141 				break;
142 			case NICK_FLOOD:
143 				flood_count = get_cset_int_var(channel->csets, KICK_ON_NICKFLOOD_CSET);
144 				break;
145 			case KICK_FLOOD:
146 				flood_count = get_cset_int_var(channel->csets, KICK_ON_KICKFLOOD_CSET);
147 				break;
148 			case DEOP_FLOOD:
149 				flood_count = get_cset_int_var(channel->csets, KICK_ON_DEOPFLOOD_CSET);
150 				break;
151 			default:
152 			break;
153 		}
154 	}
155 	else
156 	{
157 		switch(type)
158 		{
159 			case CDCC_FLOOD:
160 				flood_count = get_int_var(CDCC_FLOOD_AFTER_VAR);
161 				break;
162 			case CTCP_FLOOD:
163 				flood_count = get_int_var(CTCP_FLOOD_AFTER_VAR);
164 			case CTCP_ACTION_FLOOD:
165 			default:
166 				break;
167 		}
168 	}
169 	return flood_count;
170 }
171 #endif
172 
get_flood_val(ChannelList * chan,int type,int * flood_count,int * flood_rate)173 void get_flood_val(ChannelList *chan, int type, int *flood_count, int *flood_rate)
174 {
175 	*flood_count = get_int_var(FLOOD_AFTER_VAR);
176 	*flood_rate = get_int_var(FLOOD_RATE_VAR);
177 	if (chan)
178 	{
179 		switch(type)
180 		{
181 			case JOIN_FLOOD:
182 				*flood_count = get_cset_int_var(chan->csets, KICK_ON_JOINFLOOD_CSET);
183 				*flood_rate = get_cset_int_var(chan->csets, JOINFLOOD_TIME_CSET);
184 				break;
185 			case PUBLIC_FLOOD:
186 				*flood_count = get_cset_int_var(chan->csets, KICK_ON_PUBFLOOD_CSET);
187 				*flood_rate = get_cset_int_var(chan->csets, PUBFLOOD_TIME_CSET);
188 				break;
189 			case NICK_FLOOD:
190 				*flood_count = get_cset_int_var(chan->csets, KICK_ON_NICKFLOOD_CSET);
191 				*flood_rate = get_cset_int_var(chan->csets, NICKFLOOD_TIME_CSET);
192 				break;
193 			case KICK_FLOOD:
194 				*flood_count = get_cset_int_var(chan->csets, KICK_ON_KICKFLOOD_CSET);
195 				*flood_rate = get_cset_int_var(chan->csets, KICKFLOOD_TIME_CSET);
196 				break;
197 			case DEOP_FLOOD:
198 				*flood_count = get_cset_int_var(chan->csets, KICK_ON_DEOPFLOOD_CSET);
199 				*flood_rate = get_cset_int_var(chan->csets, DEOPFLOOD_TIME_CSET);
200 				break;
201 			default:
202 			break;
203 		}
204 	}
205 	else
206 	{
207 		switch(type)
208 		{
209 			case CDCC_FLOOD:
210 				*flood_count = get_int_var(CDCC_FLOOD_AFTER_VAR);
211 				*flood_rate = get_int_var(CDCC_FLOOD_RATE_VAR);
212 				break;
213 			case CTCP_FLOOD:
214 				*flood_count = get_int_var(CTCP_FLOOD_AFTER_VAR);
215 				*flood_rate = get_int_var(CTCP_FLOOD_RATE_VAR);
216 			case CTCP_ACTION_FLOOD:
217 			default:
218 				break;
219 		}
220 	}
221 }
222 
set_flood(int type,time_t flood_time,int reset,NickList * tmpnick)223 int set_flood(int type, time_t flood_time, int reset, NickList *tmpnick)
224 {
225 	if (!tmpnick)
226 		return 0;
227 	switch(type)
228 	{
229 		case JOIN_FLOOD:
230 			if (reset == RESET)
231 			{
232 				tmpnick->joincount = 1;
233 				tmpnick->jointime = flood_time;
234 			} else tmpnick->joincount++;
235 			break;
236 		case PUBLIC_FLOOD:
237 			if (reset == RESET)
238 			{
239 				tmpnick->floodcount = 1;
240 				tmpnick->floodtime = tmpnick->idle_time = flood_time;
241 			} else tmpnick->floodcount++;
242 			break;
243 		case NICK_FLOOD:
244 			if (reset == RESET)
245 			{
246 				tmpnick->nickcount = 1;
247 				tmpnick->nicktime = flood_time;
248 			} else tmpnick->nickcount++;
249 			break;
250 		case KICK_FLOOD:
251 			if (reset == RESET)
252 			{
253 				tmpnick->kickcount = 1;
254 				tmpnick->kicktime = flood_time;
255 			} else tmpnick->kickcount++;
256 			break;
257 		case DEOP_FLOOD:
258 			if (reset == RESET)
259 			{
260 				tmpnick->dopcount = 1;
261 				tmpnick->doptime = flood_time;
262 			} else tmpnick->dopcount++;
263 			break;
264 		default:
265 		break;
266 	}
267 	return 1;
268 }
269 
BX_is_other_flood(ChannelList * channel,NickList * tmpnick,int type,int * t_flood)270 int BX_is_other_flood(ChannelList *channel, NickList *tmpnick, int type, int *t_flood)
271 {
272 time_t diff = 0, flood_time = 0;
273 int doit = 0;
274 int count = 0;
275 int flood_rate = 0, flood_count = 0;
276 
277 	flood_time = now;
278 
279 
280 	if (!channel || !tmpnick)
281 		return 0;
282 	if (isme(tmpnick->nick))
283 		return 0;
284 	if (find_name_in_genericlist(tmpnick->nick, no_flood_list, FLOOD_HASHSIZE, 0))
285 		return 0;
286 	set_flood(type, flood_time, NO_RESET, tmpnick);
287 	switch(type)
288 	{
289 		case JOIN_FLOOD:
290 			if (!get_cset_int_var(channel->csets, JOINFLOOD_CSET))
291 				break;
292 			diff = flood_time - tmpnick->jointime;
293 			count = tmpnick->joincount;
294 			doit = 1;
295 			break;
296 		case PUBLIC_FLOOD:
297 			if (!get_cset_int_var(channel->csets, PUBFLOOD_CSET))
298 				break;
299 			diff = flood_time - tmpnick->floodtime;
300 			count = tmpnick->floodcount;
301 			doit = 1;
302 			break;
303 		case NICK_FLOOD:
304 			if (!get_cset_int_var(channel->csets, NICKFLOOD_CSET))
305 				break;
306 			diff = flood_time - tmpnick->nicktime;
307 			count = tmpnick->nickcount;
308 			doit = 1;
309 			break;
310 		case DEOP_FLOOD:
311 			if (!get_cset_int_var(channel->csets, DEOPFLOOD_CSET))
312 				break;
313 			diff = flood_time - tmpnick->doptime;
314 			count = tmpnick->dopcount;
315 			doit = 1;
316 			break;
317 		case KICK_FLOOD:
318 			if (!get_cset_int_var(channel->csets, KICKFLOOD_CSET))
319 				break;
320 			diff = flood_time - tmpnick->kicktime;
321 			count = tmpnick->kickcount;
322 			doit = 1;
323 			break;
324 		default:
325 			return 0;
326 			break;
327 	}
328 	if (doit)
329 	{
330 		int is_user = 0;
331 		if (!get_int_var(FLOOD_PROTECTION_VAR))
332 			return 0;
333 		get_flood_val(channel, type, &flood_count, &flood_rate);
334 		if ((tmpnick->userlist && (tmpnick->userlist->flags & ADD_FLOOD)))
335 			is_user = 1;
336 		if (!is_user && (count >= flood_count))
337 		{
338 			int flooded = 0;
339 			if (count >= flood_count)
340 			{
341 				if (!diff || (flood_rate && (diff < flood_rate)))
342 				{
343 					*t_flood = diff;
344 					flooded = 1;
345 					do_hook(FLOOD_LIST, "%s %s %s %s", tmpnick->nick, get_flood_types(type),channel?channel->channel:zero, tmpnick->host);
346 				}
347 				set_flood(type, flood_time, RESET, tmpnick);
348 				return flooded;
349 			}
350 			else if (diff > flood_rate)
351 				set_flood(type, flood_time, RESET, tmpnick);
352 		}
353 	}
354 	return 0;
355 }
356 
357 /*
358  * check_flooding: This checks for message flooding of the type specified for
359  * the given nickname.  This is described above.  This will return 0 if no
360  * flooding took place, or flooding is not being monitored from a certain
361  * person.  It will return 1 if flooding is being check for someone and an ON
362  * FLOOD is activated.
363  */
364 
BX_check_flooding(char * nick,int type,char * line,char * channel)365 int BX_check_flooding(char *nick, int type, char *line, char *channel)
366 {
367 static	int	users = 0,
368 		pos = 0;
369 time_t flood_time = now,
370 		diff = 0;
371 
372 Flooding 	*tmp;
373 int		flood_rate,
374 		flood_count;
375 
376 
377 	if (!(users = get_int_var(FLOOD_USERS_VAR)) || !*FromUserHost)
378 		return 1;
379 	if (find_name_in_genericlist(nick, no_flood_list, FLOOD_HASHSIZE, 0))
380 		return 1;
381 	if (!(tmp = find_name_in_floodlist(nick, FromUserHost, flood_list, FLOOD_HASHSIZE, 0)))
382 	{
383 		if (pos >= users)
384 		{
385 			pos -= remove_oldest_flood_hashlist(&flood_list[0], 0, (users + 1 - pos));
386 		}
387 		tmp = add_name_to_floodlist(nick, FromUserHost, channel, flood_list, FLOOD_HASHSIZE);
388 		tmp->type = type;
389 		tmp->cnt = 1;
390 		tmp->start = flood_time;
391 		tmp->flood = 0;
392 		pos++;
393 		return 1;
394 	}
395 	if (!(tmp->type & type))
396 	{
397 		tmp->type |= type;
398 		return 1;
399 	}
400 
401 #if 0
402 	flood_count = get_flood_count(type, NULL); /* FLOOD_AFTER_VAR */
403 	flood_rate = get_flood_rate(type, NULL); /* FLOOD_RATE_VAR */
404 #endif
405 	get_flood_val(NULL, type, &flood_count, &flood_rate);
406 	if (!flood_count || !flood_rate)
407 		return 1;
408 	tmp->cnt++;
409 	if (tmp->cnt > flood_count)
410 	{
411 		int ret;
412 		diff = flood_time - tmp->start;
413 		if (diff != 0)
414 			this_flood = (double)tmp->cnt / (double)diff;
415 		else
416 			this_flood = 0;
417 		allow_flood = (double)flood_count / (double)flood_rate;
418 		if (!diff || !this_flood || (this_flood > allow_flood))
419 		{
420 			if (tmp->flood == 0)
421 			{
422 				tmp->flood = 1;
423 				if ((ret = do_hook(FLOOD_LIST, "%s %s %s %s", nick, get_flood_types(type),channel?channel:zero, line)) != 1)
424 					return ret;
425 				switch(type)
426 				{
427 					case WALL_FLOOD:
428 					case MSG_FLOOD:
429 					case NOTICE_FLOOD:
430 					case CDCC_FLOOD:
431 					case CTCP_FLOOD:
432 						if (flood_prot(nick, FromUserHost, get_flood_types(type), type, get_int_var(IGNORE_TIME_VAR), channel))
433 							return 0;
434 						break;
435 					case CTCP_ACTION_FLOOD:
436 						if (flood_prot(nick, FromUserHost, get_flood_types(CTCP_FLOOD), type, get_int_var(IGNORE_TIME_VAR), channel))
437 							return 0;
438 					default:
439 						break;
440 				}
441 				if (get_int_var(FLOOD_WARNING_VAR))
442 					put_it("%s", convert_output_format(fget_string_var(FORMAT_FLOOD_FSET), "%s %s %s %s %s", update_clock(GET_TIME), get_flood_types(type), nick, FromUserHost, channel?channel:"unknown"));
443 			}
444 			return 1;
445 		}
446 		else
447 		{
448 			tmp->flood = 0;
449 			tmp->cnt = 1;
450 			tmp->start = flood_time;
451 		}
452 	}
453 	return 1;
454 }
455 
check_ctcp_ban_flood(char * channel,char * nick)456 void check_ctcp_ban_flood(char *channel, char *nick)
457 {
458 NickList *Nick = NULL;
459 ChannelList *chan = NULL;
460 	for (chan = get_server_channels(from_server); chan; chan = chan->next)
461 		if ((Nick = find_nicklist_in_channellist(nick, chan, 0)))
462 			break;
463 	if (chan && chan->have_op && get_cset_int_var(chan->csets, CTCP_FLOOD_BAN_CSET) && Nick)
464 	{
465 		if (!Nick->userlist || (Nick->userlist && !(Nick->userlist->flags & ADD_FLOOD)))
466 		{
467 			if (!nick_isop(Nick) || get_cset_int_var(chan->csets, KICK_OPS_CSET))
468 			{
469 				char *ban, *u, *h;
470 				u = LOCAL_COPY(Nick->host);
471 				h = strchr(u, '@');
472 				*h++ = 0;
473 				ban = ban_it(Nick->nick, u, h, Nick->ip);
474 				if (!ban_is_on_channel(ban, chan) && !eban_is_on_channel(ban, chan))
475 					send_to_server("MODE %s +b %s", chan->channel, ban);
476 			}
477 		}
478 	}
479 }
480 
BX_flood_prot(char * nick,char * userhost,char * type,int ctcp_type,int ignoretime,char * channel)481 int BX_flood_prot (char *nick, char *userhost, char *type, int ctcp_type, int ignoretime, char *channel)
482 {
483 ChannelList *chan;
484 NickList *Nick;
485 char tmp[BIG_BUFFER_SIZE+1];
486 char *uh;
487 int	old_window_display;
488 int	kick_on_flood = 1;
489 
490 	if ((ctcp_type == CDCC_FLOOD || ctcp_type == CTCP_FLOOD || ctcp_type == CTCP_ACTION_FLOOD) && !get_int_var(CTCP_FLOOD_PROTECTION_VAR))
491 		return 0;
492 	else if (!get_int_var(FLOOD_PROTECTION_VAR))
493 		return 0;
494 	else if (!my_stricmp(nick, get_server_nickname(from_server)))
495 		return 0;
496 	switch (ctcp_type)
497 	{
498 		case WALL_FLOOD:
499 		case MSG_FLOOD:
500 			break;
501 		case NOTICE_FLOOD:
502 			break;
503 		case PUBLIC_FLOOD:
504 			if (channel)
505 			{
506 				if ((chan = lookup_channel(channel, from_server, 0)))
507 				{
508 					kick_on_flood = get_cset_int_var(chan->csets, PUBFLOOD_CSET);
509 					if (kick_on_flood && (Nick = find_nicklist_in_channellist(nick, chan, 0)))
510 					{
511 						if (chan->have_op && (!Nick->userlist || (Nick->userlist && !(Nick->userlist->flags & ADD_FLOOD))))
512 							if (!nick_isop(Nick) || get_cset_int_var(chan->csets, KICK_OPS_CSET))
513 								send_to_server("KICK %s %s :\002%s\002 flooder", chan->channel, nick, type);
514 					}
515 				}
516 			}
517 			break;
518 		case CTCP_FLOOD:
519 		case CTCP_ACTION_FLOOD:
520 			check_ctcp_ban_flood(channel, nick);
521 		default:
522 			if (get_int_var(FLOOD_KICK_VAR) && kick_on_flood && channel)
523 			{
524 				for (chan = get_server_channels(from_server); chan; chan = chan->next)
525 				{
526 					if (chan->have_op && (Nick = find_nicklist_in_channellist(nick, chan, 0)))
527 					{
528 						if ((!Nick->userlist || (Nick->userlist && !(Nick->userlist->flags & ADD_FLOOD))))
529 							if (!nick_isop(Nick) || get_cset_int_var(chan->csets, KICK_OPS_CSET))
530 								send_to_server("KICK %s %s :\002%s\002 flooder", chan->channel, nick, type);
531 					}
532 				}
533 			}
534 	}
535 	if (!ignoretime)
536 		return 0;
537 	uh = clear_server_flags(userhost);
538 	snprintf(tmp, sizeof tmp, "*!*%s", uh);
539 	old_window_display = window_display;
540 	window_display = 0;
541 	ignore_nickname(tmp, ignore_type(type, strlen(type)), 0);
542 	window_display = old_window_display;
543 	snprintf(tmp, sizeof tmp, "%d ^IGNORE *!*%s NONE", ignoretime, uh);
544 	timercmd("TIMER", tmp, NULL, NULL);
545 	bitchsay("Auto-ignoring %s for %d minutes [\002%s\002 flood]", nick, ignoretime/60, type);
546 	return 1;
547 }
548 
remove_oldest_flood_hashlist(HashEntry * list,time_t timet,int count)549 static int remove_oldest_flood_hashlist(HashEntry *list, time_t timet, int count)
550 {
551 Flooding *ptr;
552 register time_t t;
553 int total = 0;
554 register unsigned long x;
555 	t = now;
556 	if (!count)
557 	{
558 		for (x = 0; x < FLOOD_HASHSIZE; x++)
559 		{
560 			ptr = (Flooding *) (list + x)->list;
561 			if (!ptr || !*ptr->name)
562 				continue;
563 			while (ptr)
564 			{
565 				if ((ptr->start + timet) <= t)
566 				{
567 					if (!(ptr = find_name_in_floodlist(ptr->name, ptr->host, flood_list, FLOOD_HASHSIZE, 1)))
568 						continue;
569 					new_free(&(ptr->channel));
570 					new_free(&(ptr->name));
571 					new_free(&ptr->host);
572 					new_free((char **)&ptr);
573 					total++;
574 					ptr = (Flooding *) (list + x)->list;
575 				} else ptr = ptr->next;
576 			}
577 		}
578 	}
579 	else
580 	{
581 		for (x = 0; x < FLOOD_HASHSIZE; x++)
582 		{
583 			Flooding *next = NULL;
584 			ptr = (Flooding *) (list + x)->list;
585 			if (!ptr || !*ptr->name)
586 				continue;
587 			while(ptr && count)
588 			{
589 				if ((ptr = find_name_in_floodlist(ptr->name, ptr->host, flood_list, FLOOD_HASHSIZE, 1)))
590 				{
591 					next = ptr->next;
592 					new_free(&(ptr->channel));
593 					new_free(&(ptr->name));
594 					new_free(&ptr->host);
595 					new_free((char **)&ptr);
596 					total++; count--;
597 					ptr = (Flooding *) (list + x)->list;
598 					ptr = next;
599 				}
600 			}
601 		}
602 	}
603 	return total;
604 }
605 
clean_flood_list()606 void clean_flood_list()
607 {
608 	remove_oldest_flood_hashlist(&flood_list[0], get_int_var(FLOOD_RATE_VAR)+1, 0);
609 }
610