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