542 lines
14 KiB
C
542 lines
14 KiB
C
/*
|
|
* flood.c: handle channel flooding.
|
|
*
|
|
* This attempts to give you some protection from flooding. Basically, it keeps
|
|
* track of how far apart (timewise) messages come in from different people.
|
|
* If a single nickname sends more than 3 messages in a row in under a
|
|
* second, this is considered flooding. It then activates the ON FLOOD with
|
|
* the nickname and type (appropriate for use with IGNORE).
|
|
*
|
|
* Thanks to Tomi Ollila <f36664r@puukko.hut.fi> for this one.
|
|
*/
|
|
|
|
|
|
#include "irc.h"
|
|
static char cvsrevision[] = "$Id$";
|
|
CVS_REVISION(flood_c)
|
|
#include "struct.h"
|
|
|
|
#include "alias.h"
|
|
#include "hook.h"
|
|
#include "ircaux.h"
|
|
#include "ignore.h"
|
|
#include "flood.h"
|
|
#include "vars.h"
|
|
#include "output.h"
|
|
#include "list.h"
|
|
#include "misc.h"
|
|
#include "server.h"
|
|
#include "userlist.h"
|
|
#include "timer.h"
|
|
#include "ignore.h"
|
|
#include "status.h"
|
|
#include "hash2.h"
|
|
#include "cset.h"
|
|
#define MAIN_SOURCE
|
|
#include "modval.h"
|
|
|
|
static const struct flood_table {
|
|
const char *text;
|
|
long ig_type;
|
|
} flood_table[] =
|
|
{
|
|
{ "MSG", IGNORE_MSGS },
|
|
{ "PUBLIC", IGNORE_PUBLIC },
|
|
{ "NOTICE", IGNORE_NOTICES },
|
|
{ "WALL", IGNORE_WALLS },
|
|
{ "WALLOP", IGNORE_WALLOPS },
|
|
{ "CTCP", IGNORE_CTCPS },
|
|
{ "INVITE", IGNORE_INVITES },
|
|
{ "CDCC", IGNORE_CDCC },
|
|
{ "ACTION", IGNORE_CTCPS },
|
|
/* Flood types below here do not set ignores */
|
|
{ "NICK", IGNORE_NICKS },
|
|
{ "DEOP", IGNORE_MODES },
|
|
{ "KICK", IGNORE_KICKS },
|
|
{ "JOIN", IGNORE_JOINS }
|
|
};
|
|
|
|
#define FLOOD_HASHSIZE 31
|
|
HashEntry no_flood_list[FLOOD_HASHSIZE];
|
|
HashEntry flood_list[FLOOD_HASHSIZE];
|
|
|
|
extern char *FromUserHost;
|
|
extern unsigned int window_display;
|
|
extern int from_server;
|
|
|
|
#define NO_RESET 0
|
|
#define RESET 1
|
|
|
|
#if 0
|
|
int get_flood_rate(int type, ChannelList * channel)
|
|
{
|
|
int flood_rate = get_int_var(FLOOD_RATE_VAR);
|
|
if (channel)
|
|
{
|
|
switch(type)
|
|
{
|
|
case JOIN_FLOOD:
|
|
flood_rate = get_cset_int_var(channel->csets, JOINFLOOD_TIME_CSET);
|
|
break;
|
|
case PUBLIC_FLOOD:
|
|
flood_rate = get_cset_int_var(channel->csets, PUBFLOOD_TIME_CSET);
|
|
break;
|
|
case NICK_FLOOD:
|
|
flood_rate = get_cset_int_var(channel->csets, NICKFLOOD_TIME_CSET);
|
|
break;
|
|
case KICK_FLOOD:
|
|
flood_rate = get_cset_int_var(channel->csets, KICKFLOOD_TIME_CSET);
|
|
break;
|
|
case DEOP_FLOOD:
|
|
flood_rate = get_cset_int_var(channel->csets, DEOPFLOOD_TIME_CSET);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch(type)
|
|
{
|
|
case CDCC_FLOOD:
|
|
flood_rate = get_int_var(CDCC_FLOOD_RATE_VAR);
|
|
break;
|
|
case CTCP_FLOOD:
|
|
flood_rate = get_int_var(CTCP_FLOOD_RATE_VAR);
|
|
case CTCP_ACTION_FLOOD:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return flood_rate;
|
|
}
|
|
|
|
int get_flood_count(int type, ChannelList * channel)
|
|
{
|
|
int flood_count = get_int_var(FLOOD_AFTER_VAR);
|
|
if (channel) {
|
|
switch(type)
|
|
{
|
|
case JOIN_FLOOD:
|
|
flood_count = get_cset_int_var(channel->csets, KICK_ON_JOINFLOOD_CSET);
|
|
break;
|
|
case PUBLIC_FLOOD:
|
|
flood_count = get_cset_int_var(channel->csets, KICK_ON_PUBFLOOD_CSET);
|
|
break;
|
|
case NICK_FLOOD:
|
|
flood_count = get_cset_int_var(channel->csets, KICK_ON_NICKFLOOD_CSET);
|
|
break;
|
|
case KICK_FLOOD:
|
|
flood_count = get_cset_int_var(channel->csets, KICK_ON_KICKFLOOD_CSET);
|
|
break;
|
|
case DEOP_FLOOD:
|
|
flood_count = get_cset_int_var(channel->csets, KICK_ON_DEOPFLOOD_CSET);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch(type)
|
|
{
|
|
case CDCC_FLOOD:
|
|
flood_count = get_int_var(CDCC_FLOOD_AFTER_VAR);
|
|
break;
|
|
case CTCP_FLOOD:
|
|
flood_count = get_int_var(CTCP_FLOOD_AFTER_VAR);
|
|
case CTCP_ACTION_FLOOD:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
return flood_count;
|
|
}
|
|
#endif
|
|
|
|
static void get_flood_val(ChannelList *chan, enum flood_type type, int *flood_count, int *flood_rate)
|
|
{
|
|
*flood_count = get_int_var(FLOOD_AFTER_VAR);
|
|
*flood_rate = get_int_var(FLOOD_RATE_VAR);
|
|
if (chan)
|
|
{
|
|
switch(type)
|
|
{
|
|
case JOIN_FLOOD:
|
|
*flood_count = get_cset_int_var(chan->csets, KICK_ON_JOINFLOOD_CSET);
|
|
*flood_rate = get_cset_int_var(chan->csets, JOINFLOOD_TIME_CSET);
|
|
break;
|
|
case PUBLIC_FLOOD:
|
|
*flood_count = get_cset_int_var(chan->csets, KICK_ON_PUBFLOOD_CSET);
|
|
*flood_rate = get_cset_int_var(chan->csets, PUBFLOOD_TIME_CSET);
|
|
break;
|
|
case NICK_FLOOD:
|
|
*flood_count = get_cset_int_var(chan->csets, KICK_ON_NICKFLOOD_CSET);
|
|
*flood_rate = get_cset_int_var(chan->csets, NICKFLOOD_TIME_CSET);
|
|
break;
|
|
case KICK_FLOOD:
|
|
*flood_count = get_cset_int_var(chan->csets, KICK_ON_KICKFLOOD_CSET);
|
|
*flood_rate = get_cset_int_var(chan->csets, KICKFLOOD_TIME_CSET);
|
|
break;
|
|
case DEOP_FLOOD:
|
|
*flood_count = get_cset_int_var(chan->csets, KICK_ON_DEOPFLOOD_CSET);
|
|
*flood_rate = get_cset_int_var(chan->csets, DEOPFLOOD_TIME_CSET);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch(type)
|
|
{
|
|
case CDCC_FLOOD:
|
|
*flood_count = get_int_var(CDCC_FLOOD_AFTER_VAR);
|
|
*flood_rate = get_int_var(CDCC_FLOOD_RATE_VAR);
|
|
break;
|
|
case CTCP_FLOOD:
|
|
*flood_count = get_int_var(CTCP_FLOOD_AFTER_VAR);
|
|
*flood_rate = get_int_var(CTCP_FLOOD_RATE_VAR);
|
|
case CTCP_ACTION_FLOOD:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int set_flood(enum flood_type type, time_t flood_time, int reset, NickList *tmpnick)
|
|
{
|
|
if (!tmpnick)
|
|
return 0;
|
|
switch(type)
|
|
{
|
|
case JOIN_FLOOD:
|
|
if (reset == RESET)
|
|
{
|
|
tmpnick->joincount = 1;
|
|
tmpnick->jointime = flood_time;
|
|
} else tmpnick->joincount++;
|
|
break;
|
|
case PUBLIC_FLOOD:
|
|
if (reset == RESET)
|
|
{
|
|
tmpnick->floodcount = 1;
|
|
tmpnick->floodtime = tmpnick->idle_time = flood_time;
|
|
} else tmpnick->floodcount++;
|
|
break;
|
|
case NICK_FLOOD:
|
|
if (reset == RESET)
|
|
{
|
|
tmpnick->nickcount = 1;
|
|
tmpnick->nicktime = flood_time;
|
|
} else tmpnick->nickcount++;
|
|
break;
|
|
case KICK_FLOOD:
|
|
if (reset == RESET)
|
|
{
|
|
tmpnick->kickcount = 1;
|
|
tmpnick->kicktime = flood_time;
|
|
} else tmpnick->kickcount++;
|
|
break;
|
|
case DEOP_FLOOD:
|
|
if (reset == RESET)
|
|
{
|
|
tmpnick->dopcount = 1;
|
|
tmpnick->doptime = flood_time;
|
|
} else tmpnick->dopcount++;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int BX_is_other_flood(ChannelList *channel, NickList *tmpnick, enum flood_type type, int *t_flood)
|
|
{
|
|
time_t diff = 0, flood_time = 0;
|
|
int doit = 0;
|
|
int count = 0;
|
|
int flood_rate = 0, flood_count = 0;
|
|
|
|
flood_time = now;
|
|
|
|
|
|
if (!channel || !tmpnick)
|
|
return 0;
|
|
if (isme(tmpnick->nick))
|
|
return 0;
|
|
if (find_name_in_genericlist(tmpnick->nick, no_flood_list, FLOOD_HASHSIZE, 0))
|
|
return 0;
|
|
set_flood(type, flood_time, NO_RESET, tmpnick);
|
|
switch(type)
|
|
{
|
|
case JOIN_FLOOD:
|
|
if (!get_cset_int_var(channel->csets, JOINFLOOD_CSET))
|
|
break;
|
|
diff = flood_time - tmpnick->jointime;
|
|
count = tmpnick->joincount;
|
|
doit = 1;
|
|
break;
|
|
case PUBLIC_FLOOD:
|
|
if (!get_cset_int_var(channel->csets, PUBFLOOD_CSET))
|
|
break;
|
|
diff = flood_time - tmpnick->floodtime;
|
|
count = tmpnick->floodcount;
|
|
doit = 1;
|
|
break;
|
|
case NICK_FLOOD:
|
|
if (!get_cset_int_var(channel->csets, NICKFLOOD_CSET))
|
|
break;
|
|
diff = flood_time - tmpnick->nicktime;
|
|
count = tmpnick->nickcount;
|
|
doit = 1;
|
|
break;
|
|
case DEOP_FLOOD:
|
|
if (!get_cset_int_var(channel->csets, DEOPFLOOD_CSET))
|
|
break;
|
|
diff = flood_time - tmpnick->doptime;
|
|
count = tmpnick->dopcount;
|
|
doit = 1;
|
|
break;
|
|
case KICK_FLOOD:
|
|
if (!get_cset_int_var(channel->csets, KICKFLOOD_CSET))
|
|
break;
|
|
diff = flood_time - tmpnick->kicktime;
|
|
count = tmpnick->kickcount;
|
|
doit = 1;
|
|
break;
|
|
default:
|
|
return 0;
|
|
break;
|
|
}
|
|
if (doit)
|
|
{
|
|
int is_user = 0;
|
|
if (!get_int_var(FLOOD_PROTECTION_VAR))
|
|
return 0;
|
|
get_flood_val(channel, type, &flood_count, &flood_rate);
|
|
if ((tmpnick->userlist && (tmpnick->userlist->flags & ADD_FLOOD)))
|
|
is_user = 1;
|
|
if (!is_user && (count >= flood_count))
|
|
{
|
|
int flooded = 0;
|
|
if (count >= flood_count)
|
|
{
|
|
if (!diff || (flood_rate && (diff < flood_rate)))
|
|
{
|
|
*t_flood = diff;
|
|
flooded = 1;
|
|
do_hook(FLOOD_LIST, "%s %s %s %s", tmpnick->nick, flood_table[type].text,channel?channel->channel:zero, tmpnick->host);
|
|
}
|
|
set_flood(type, flood_time, RESET, tmpnick);
|
|
return flooded;
|
|
}
|
|
else if (diff > flood_rate)
|
|
set_flood(type, flood_time, RESET, tmpnick);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Prune any flood entries older than FLOOD_RATE_VAR (or from the future, in case the clock has
|
|
* gone backwards). */
|
|
static int remove_oldest_flood_hashlist(HashEntry *list)
|
|
{
|
|
const double flood_rate = get_int_var(FLOOD_RATE_VAR);
|
|
Flooding *ptr;
|
|
int total = 0;
|
|
unsigned long x;
|
|
|
|
for (x = 0; x < FLOOD_HASHSIZE; x++)
|
|
{
|
|
ptr = list[x].list;
|
|
if (!ptr || !*ptr->name)
|
|
continue;
|
|
while (ptr)
|
|
{
|
|
double elapsed = time_since(&ptr->start);
|
|
if (elapsed > flood_rate || elapsed < 0)
|
|
{
|
|
if (!(ptr = find_name_in_floodlist(ptr->name, ptr->host, flood_list, FLOOD_HASHSIZE, 1)))
|
|
continue;
|
|
new_free(&(ptr->channel));
|
|
new_free(&(ptr->name));
|
|
new_free(&ptr->host);
|
|
new_free((char **)&ptr);
|
|
total++;
|
|
ptr = list[x].list;
|
|
} else ptr = ptr->next;
|
|
}
|
|
}
|
|
return total;
|
|
}
|
|
|
|
/*
|
|
* check_flooding: This checks for message flooding of the type specified for
|
|
* the given nickname. This is described above. This will return 0 if no
|
|
* flooding took place, or flooding is not being monitored from a certain
|
|
* person. It will return 1 if flooding is being check for someone and an ON
|
|
* FLOOD is activated.
|
|
*/
|
|
|
|
int BX_check_flooding(char *nick, enum flood_type type, char *line, char *channel)
|
|
{
|
|
Flooding *tmp;
|
|
int flood_rate;
|
|
int flood_count;
|
|
|
|
if (!get_int_var(FLOOD_USERS_VAR) || !*FromUserHost)
|
|
return 1;
|
|
if (find_name_in_genericlist(nick, no_flood_list, FLOOD_HASHSIZE, 0))
|
|
return 1;
|
|
if (!(tmp = find_name_in_floodlist(nick, FromUserHost, flood_list, FLOOD_HASHSIZE, 0)))
|
|
{
|
|
remove_oldest_flood_hashlist(&flood_list[0]);
|
|
tmp = add_name_to_floodlist(nick, FromUserHost, channel, flood_list, FLOOD_HASHSIZE);
|
|
tmp->flags = FLOOD_FLAG(type);
|
|
tmp->cnt = 1;
|
|
get_time(&tmp->start);
|
|
tmp->flood = 0;
|
|
return 1;
|
|
}
|
|
if (!(tmp->flags & FLOOD_FLAG(type)))
|
|
{
|
|
tmp->flags |= FLOOD_FLAG(type);
|
|
return 1;
|
|
}
|
|
|
|
get_flood_val(NULL, type, &flood_count, &flood_rate);
|
|
if (!flood_count || !flood_rate)
|
|
return 1;
|
|
tmp->cnt++;
|
|
if (tmp->cnt > flood_count)
|
|
{
|
|
int ret;
|
|
double diff = time_since(&tmp->start);
|
|
double allow_flood = (double)flood_count / (double)flood_rate;
|
|
double this_flood = 0.0;
|
|
|
|
if (diff != 0)
|
|
this_flood = (double)tmp->cnt / diff;
|
|
if (!diff || this_flood > allow_flood)
|
|
{
|
|
if (tmp->flood == 0)
|
|
{
|
|
tmp->flood = 1;
|
|
if ((ret = do_hook(FLOOD_LIST, "%s %s %s %s", nick, flood_table[type].text, channel?channel:zero, line)) != 1)
|
|
return ret;
|
|
if (flood_prot(nick, FromUserHost, type, get_int_var(IGNORE_TIME_VAR), channel))
|
|
return 0;
|
|
if (get_int_var(FLOOD_WARNING_VAR))
|
|
put_it("%s", convert_output_format(fget_string_var(FORMAT_FLOOD_FSET), "%s %s %s %s %s", update_clock(GET_TIME), flood_table[type].text, nick, FromUserHost, channel?channel:"unknown"));
|
|
}
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
tmp->flood = 0;
|
|
tmp->cnt = 1;
|
|
get_time(&tmp->start);
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static void check_ctcp_ban_flood(char *channel, char *nick)
|
|
{
|
|
NickList *Nick = NULL;
|
|
ChannelList *chan = NULL;
|
|
for (chan = get_server_channels(from_server); chan; chan = chan->next)
|
|
if ((Nick = find_nicklist_in_channellist(nick, chan, 0)))
|
|
break;
|
|
if (chan && chan->have_op && get_cset_int_var(chan->csets, CTCP_FLOOD_BAN_CSET) && Nick)
|
|
{
|
|
if (!Nick->userlist || (Nick->userlist && !(Nick->userlist->flags & ADD_FLOOD)))
|
|
{
|
|
if (!nick_isop(Nick) || get_cset_int_var(chan->csets, KICK_OPS_CSET))
|
|
{
|
|
char *ban, *u, *h;
|
|
u = LOCAL_COPY(Nick->host);
|
|
h = strchr(u, '@');
|
|
*h++ = 0;
|
|
ban = ban_it(Nick->nick, u, h, Nick->ip);
|
|
if (!ban_is_on_channel(ban, chan) && !eban_is_on_channel(ban, chan))
|
|
send_to_server("MODE %s +b %s", chan->channel, ban);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int BX_flood_prot(char *nick, char *userhost, enum flood_type flood_type, int ignoretime, char *channel)
|
|
{
|
|
ChannelList *chan;
|
|
NickList *Nick;
|
|
char tmp[BIG_BUFFER_SIZE+1];
|
|
char *uh;
|
|
int old_window_display;
|
|
int kick_on_flood = 1;
|
|
|
|
if ((flood_type == CDCC_FLOOD || flood_type == CTCP_FLOOD || flood_type == CTCP_ACTION_FLOOD) && !get_int_var(CTCP_FLOOD_PROTECTION_VAR))
|
|
return 0;
|
|
else if (!get_int_var(FLOOD_PROTECTION_VAR))
|
|
return 0;
|
|
else if (!my_stricmp(nick, get_server_nickname(from_server)))
|
|
return 0;
|
|
switch (flood_type)
|
|
{
|
|
case WALL_FLOOD:
|
|
case MSG_FLOOD:
|
|
break;
|
|
case NOTICE_FLOOD:
|
|
break;
|
|
case PUBLIC_FLOOD:
|
|
if (channel)
|
|
{
|
|
if ((chan = lookup_channel(channel, from_server, 0)))
|
|
{
|
|
kick_on_flood = get_cset_int_var(chan->csets, PUBFLOOD_CSET);
|
|
if (kick_on_flood && (Nick = find_nicklist_in_channellist(nick, chan, 0)))
|
|
{
|
|
if (chan->have_op && (!Nick->userlist || (Nick->userlist && !(Nick->userlist->flags & ADD_FLOOD))))
|
|
if (!nick_isop(Nick) || get_cset_int_var(chan->csets, KICK_OPS_CSET))
|
|
send_to_server("KICK %s %s :\002%s\002 flooder", chan->channel, nick, flood_table[flood_type].text);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case CTCP_FLOOD:
|
|
case CTCP_ACTION_FLOOD:
|
|
check_ctcp_ban_flood(channel, nick);
|
|
default:
|
|
if (get_int_var(FLOOD_KICK_VAR) && kick_on_flood && channel)
|
|
{
|
|
for (chan = get_server_channels(from_server); chan; chan = chan->next)
|
|
{
|
|
if (chan->have_op && (Nick = find_nicklist_in_channellist(nick, chan, 0)))
|
|
{
|
|
if ((!Nick->userlist || (Nick->userlist && !(Nick->userlist->flags & ADD_FLOOD))))
|
|
if (!nick_isop(Nick) || get_cset_int_var(chan->csets, KICK_OPS_CSET))
|
|
send_to_server("KICK %s %s :\002%s\002 flooder", chan->channel, nick, flood_table[flood_type].text);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!ignoretime)
|
|
return 0;
|
|
uh = clear_server_flags(userhost);
|
|
snprintf(tmp, sizeof tmp, "*!*%s", uh);
|
|
old_window_display = window_display;
|
|
window_display = 0;
|
|
ignore_nickname(tmp, flood_table[flood_type].ig_type, 0);
|
|
window_display = old_window_display;
|
|
snprintf(tmp, sizeof tmp, "%d ^IGNORE *!*%s NONE", ignoretime, uh);
|
|
timercmd("TIMER", tmp, NULL, NULL);
|
|
bitchsay("Auto-ignoring %s for %d minutes [\002%s\002 flood]", nick, ignoretime/60, flood_table[flood_type].text);
|
|
return 1;
|
|
}
|
|
|
|
void clean_flood_list()
|
|
{
|
|
remove_oldest_flood_hashlist(&flood_list[0]);
|
|
}
|