added simple spam/flood scanner to bot_NeonSpam
authorpk910 <philipp@zoelle1.de>
Wed, 19 Oct 2011 13:59:07 +0000 (15:59 +0200)
committerpk910 <philipp@zoelle1.de>
Wed, 19 Oct 2011 14:35:05 +0000 (16:35 +0200)
src/bot_NeonSpam.c
src/bot_NeonSpam.h
src/event_neonspam_chanmsg.c [new file with mode: 0644]
src/tools.c
src/tools.h

index 19a14b0ac638aa5430559950168ea144f6767867..bf2ae92c6392712140a3927e5e514cd765263ec3 100644 (file)
  */
 
 #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);
index 9b169f90eccb4f1426e834562abb27c9c6b530a4..723f1a26cf13ed6f9c422fd4fceb8dae59a4c07f 100644 (file)
 #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 (file)
index 0000000..87ecdc6
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>. 
+ */
+
+#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;
+}
+
+
index e2637cc84737ab7d32f485158767fec240b87efd..3e6a2fc2d8bb547f62f5a4420e24d2e3f7d2b1a0 100644 (file)
@@ -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();
 }
index 5ece4af925d06fc67307208663ac74bf6c671e9d..157d50d22cd8224225bd578e57b29753c1881460 100644 (file)
@@ -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