bitchx/source/flood.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]);
}