From 744387b352bc4d7d42fd6143d8583f3c64e56b9e Mon Sep 17 00:00:00 2001 From: pk910 Date: Wed, 19 Oct 2011 15:59:07 +0200 Subject: [PATCH] added simple spam/flood scanner to bot_NeonSpam --- src/bot_NeonSpam.c | 52 +++++++++++++++- src/bot_NeonSpam.h | 17 +++++- src/event_neonspam_chanmsg.c | 115 +++++++++++++++++++++++++++++++++++ src/tools.c | 26 ++++++++ src/tools.h | 2 + 5 files changed, 209 insertions(+), 3 deletions(-) create mode 100644 src/event_neonspam_chanmsg.c diff --git a/src/bot_NeonSpam.c b/src/bot_NeonSpam.c index 19a14b0..bf2ae92 100644 --- a/src/bot_NeonSpam.c +++ b/src/bot_NeonSpam.c @@ -16,6 +16,25 @@ */ #include "bot_NeonSpam.h" +#include "modcmd.h" +#include "IRCParser.h" +#include "IRCEvents.h" +#include "UserNode.h" +#include "ChanNode.h" +#include "ChanUser.h" +#include "ModeNode.h" +#include "BanNode.h" +#include "ClientSocket.h" +#include "mysqlConn.h" +#include "lang.h" +#include "HandleInfoHandler.h" +#include "WHOHandler.h" +#include "DBHelper.h" +#include "tools.h" +#include "timeq.h" +#include "version.h" +#include "EventLogger.h" +#include "bots.h" #include "cmd_neonserv.h" #define BOTID 2 @@ -24,8 +43,12 @@ static const struct default_language_entry msgtab[] = { {NULL, NULL} }; +static int loadNeonSpamSettings(struct ChanNode *chan); +static void createSpamNode(struct ChanUser *chanuser); + //EVENTS //#include "event_neonspam_join.c" +#include "event_neonspam_chanmsg.c" static void neonspam_bot_ready(struct ClientSocket *client) { MYSQL_RES *res; @@ -91,7 +114,33 @@ static void start_bots() { } } } - +} + +static int loadNeonSpamSettings(struct ChanNode *chan) { + struct NeonSpamSettings *settings = malloc(sizeof(*settings)); + if(!settings) { + perror("malloc() failed"); + return 0; + } + settings->flags = SPAMSETTINGS_SCANVOICE | SPAMSETTINGS_FLOODSCAN | SPAMSETTINGS_SPAMSCAN; + settings->spam_amount = 3; + settings->flood_amount = 4; + settings->flood_time = 5; + chan->spam_settings = settings; + return 1; +} + +static void createSpamNode(struct ChanUser *chanuser) { + struct NeonSpamNode *spamnode = malloc(sizeof(*spamnode)); + if(!spamnode) { + perror("malloc() failed"); + return; + } + spamnode->lastmsg = 0; + spamnode->spamcount = 0; + spamnode->floodpenalty = 0; + spamnode->last_penalty_update = time(0); + chanuser->spamnode = spamnode; } void init_NeonSpam() { @@ -115,6 +164,7 @@ void init_NeonSpam() { //register events bind_bot_ready(neonspam_bot_ready); //bind_join(neonspam_event_join); + bind_chanmsg(neonspam_event_chanmsg); bind_privctcp(general_event_privctcp); set_trigger_callback(BOTID, neonspam_trigger_callback); diff --git a/src/bot_NeonSpam.h b/src/bot_NeonSpam.h index 9b169f9..723f1a2 100644 --- a/src/bot_NeonSpam.h +++ b/src/bot_NeonSpam.h @@ -26,17 +26,30 @@ #define SPAMSETTINGS_SCANOPS 0x08 #define SPAMSETTINGS_SCANVOICE 0x10 +#define MAX_FLOOD_TIME 200 + struct NeonSpamSettings { unsigned int flags; unsigned char spam_amount; - unsigned char spam_time; unsigned char flood_amount; unsigned char flood_time; }; +/* PENALTY SYSTEM +* user gets MAX_FLOOD_TIME points per message +* points get removed each loop +* pounts to be removed each second: +* MAX_FLOOD_TIME/flood_time +* +* the floodlimit is reached, if the penalty points +* are bigger than MAX_FLOOD_TIME * flood_amount +*/ struct NeonSpamNode { - unsigned int flags; + unsigned long lastmsg; //crc32 hash + unsigned char spamcount; + int floodpenalty; + time_t last_penalty_update; }; void init_NeonSpam(); diff --git a/src/event_neonspam_chanmsg.c b/src/event_neonspam_chanmsg.c new file mode 100644 index 0000000..87ecdc6 --- /dev/null +++ b/src/event_neonspam_chanmsg.c @@ -0,0 +1,115 @@ +/* event_neonspam_chanmsg.c - NeonServ v5.1 + * Copyright (C) 2011 Philipp Kreil (pk910) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#define SPAMSERV_CHECK_IGNORE 0 +#define SPAMSERV_CHECK_WARN 1 +#define SPAMSERV_CHECK_PUNISH 2 + +static int neonspam_spamscan(struct NeonSpamSettings *settings, struct ChanUser *chanuser, char *message); +static int neonspam_floodscan(struct NeonSpamSettings *settings, struct ChanUser *chanuser); + +static void neonspam_event_chanmsg(struct UserNode *user, struct ChanNode *chan, char *message) { + struct ClientSocket *client = getChannelBot(chan, BOTID); + if(!client) return; //we can't "see" this event + loadNeonSpamSettings(chan); + struct NeonSpamSettings *settings = chan->spam_settings; + struct ChanUser *chanuser = getChanUser(user, chan); + if(!settings || !chanuser) return; + + //ignore messages from ops/voices if we ignore them + if(!(settings->flags & SPAMSETTINGS_SCANOPS) && (chanuser->flags & CHANUSERFLAG_OPPED)) return; + if(!(settings->flags & SPAMSETTINGS_SCANVOICE) && (chanuser->flags & CHANUSERFLAG_VOICED)) return; + + //scan the message + int result = 0; + if(settings->flags & SPAMSETTINGS_SPAMSCAN) { + result = neonspam_spamscan(settings, chanuser, message); + switch(result) { + case SPAMSERV_CHECK_IGNORE: + break; + case SPAMSERV_CHECK_WARN: + reply(client, user, "SPAM WARNING!"); + break; + case SPAMSERV_CHECK_PUNISH: + reply(client, user, "SPAM PUNISHMENT!"); + break; + } + } + if(settings->flags & SPAMSETTINGS_FLOODSCAN) { + result = neonspam_floodscan(settings, chanuser); + switch(result) { + case SPAMSERV_CHECK_IGNORE: + break; + case SPAMSERV_CHECK_WARN: + reply(client, user, "FLOOD WARNING!"); + break; + case SPAMSERV_CHECK_PUNISH: + reply(client, user, "FLOOD PUNISHMENT!"); + break; + } + } + +} + +static int neonspam_spamscan(struct NeonSpamSettings *settings, struct ChanUser *chanuser, char *message) { + //crc32 hash of the message + unsigned long msghash = crc32(message); + if(chanuser->spamnode) { + if(chanuser->spamnode->lastmsg == msghash) { + chanuser->spamnode->spamcount++; + if(chanuser->spamnode->spamcount == settings->spam_amount) + return SPAMSERV_CHECK_WARN; + else if(chanuser->spamnode->spamcount > settings->spam_amount) + return SPAMSERV_CHECK_PUNISH; + else + return SPAMSERV_CHECK_IGNORE; + } + } else + createSpamNode(chanuser); + chanuser->spamnode->lastmsg = msghash; + chanuser->spamnode->spamcount = 1; + return SPAMSERV_CHECK_IGNORE; +} + +static int neonspam_update_penalty(struct NeonSpamSettings *settings, struct ChanUser *chanuser, int addmsg) { + int last_update = time(0) - chanuser->spamnode->last_penalty_update; + if(last_update) { + if(last_update < MAX_FLOOD_TIME && chanuser->spamnode->floodpenalty) { + chanuser->spamnode->floodpenalty -= last_update * (MAX_FLOOD_TIME / settings->flood_time); + if(chanuser->spamnode->floodpenalty < 0) + chanuser->spamnode->floodpenalty = 0; + } else + chanuser->spamnode->floodpenalty = 0; + chanuser->spamnode->last_penalty_update = time(0); + } + chanuser->spamnode->floodpenalty += MAX_FLOOD_TIME * addmsg; + return chanuser->spamnode->floodpenalty / MAX_FLOOD_TIME + ((chanuser->spamnode->floodpenalty % MAX_FLOOD_TIME) ? 1 : 0); +} + +static int neonspam_floodscan(struct NeonSpamSettings *settings, struct ChanUser *chanuser) { + if(!chanuser->spamnode) + createSpamNode(chanuser); + int messages_pending = neonspam_update_penalty(settings, chanuser, 1); + if(messages_pending == settings->flood_amount) + return SPAMSERV_CHECK_WARN; + else if(messages_pending > settings->flood_amount) + return SPAMSERV_CHECK_PUNISH; + else + return SPAMSERV_CHECK_IGNORE; +} + + diff --git a/src/tools.c b/src/tools.c index e2637cc..3e6a2fc 100644 --- a/src/tools.c +++ b/src/tools.c @@ -513,6 +513,32 @@ int isFakeHost(char *host) { return (strlen(p2+1) > 4); } +static unsigned long crc_table[256]; + +static void crc32_init() { + unsigned long crc; + int i, j; + for(i = 0; i < 256; i++) { + crc = i; + for(j = 8; j > 0; j--) { + if(crc & 1) + crc = (crc >> 1) ^ 0xEDB88320L; + else + crc >>= 1; + } + crc_table[i] = crc; + } +} + +unsigned long crc32(const char *text) { + register unsigned long crc = 0xFFFFFFFF; + unsigned int c, i = 0; + while((c = (unsigned int)text[i++]) != 0) + crc = ((crc >> 8) & 0x00FFFFFF) ^ crc_table[(crc^c) & 0xFF]; + return (crc^0xFFFFFFFF); +} + void init_tools() { register_default_language_table(msgtab); + crc32_init(); } diff --git a/src/tools.h b/src/tools.h index 5ece4af..157d50d 100644 --- a/src/tools.h +++ b/src/tools.h @@ -82,6 +82,8 @@ char* generate_banmask(struct UserNode *user, char *buffer); char* make_banmask(char *input, char* buffer); int isFakeHost(char *host); +unsigned long crc32(const char *text); + void init_tools(); #endif \ No newline at end of file -- 2.20.1