瀏覽代碼

Merge branch 'ducch-8-caps-flood'

* ducch-8-caps-flood:
  Don't count color/bold chars when calculating CAPS percentage (#8)
  Only calculate if needed (#8)
  Return 1 if member is kicked (#8)
  Don't count spaces for caps percentage (#8)
  Log caps/color kicks in LOG_MODES/chan (#8)
  Use a double for percentage tracking (#8)
  Fix sanity check to return if not capslimit and not colorlimit (#8)
  Count bolds as well as colors (#8)
  Cleaup code slightly
  Proper kick-reasons (#8)
  Added consideration for flood-exempt and fellow bots (#8)
  Added chanset 'colorlimit' (#8)
  Added chanset 'capsflood' (#8)

Conflicts:
	src/mod/channels.mod/chanmisc.c
	src/mod/channels.mod/userchan.c
Bryan Drewery 14 年之前
父節點
當前提交
9a3263df68

+ 2 - 0
doc/UPDATES

@@ -43,6 +43,8 @@
   * Bot will now lockdown channel (+im) if a drone flood is detected (#37)
     * Add chansets 'flood-mchan', 'flood-mbytes', 'flood-mctcp' to control reactions to mass floods (#37)
   * Remove unneeded chanset 'nomassjoin'
+  * Add chanset 'capslimit' to handle % of message that can be in caps before kick (#8)
+  * Add chanset 'colorlimit' to handle how many color codes are allowed in a message before kick (#8)
 
 1.3.4 - http://wraith.botpack.net/milestone/1.3.4
   * Fix various compile warnings with newer GCC

+ 2 - 0
src/chan.h

@@ -190,6 +190,8 @@ struct chanset_t {
   interval_t flood_mjoin_time;
   int flood_mjoin_thr;
   int limitraise;
+  int capslimit;
+  int colorlimit;
   int checklimit;
   int closed_ban;
   int closed_private;

+ 25 - 1
src/mod/channels.mod/chanmisc.c

@@ -422,7 +422,29 @@ int channel_modify(char *result, struct chanset_t *chan, int items, char **item,
         return ERROR;
       }
       chan->invite_time = atoi(item[i]);
-    } else if (!strcmp(item[i], "closed-ban")) {
+    } else if (!strcmp(item[i], "capslimit") || !strcmp(item[i], "caps-limit")) {
+      i++;
+      if (i >= items) {
+        if (result)
+          strlcpy(result, "channel capslimit needs argument", RESULT_LEN);
+        return ERROR;
+      }
+      int capslimit = atoi(item[i]);
+      if (capslimit > 100 || capslimit < 0 || item[i][0] == '-') {
+        if (result)
+          strlcpy(result, "channel capslimit out of range (0-100)", RESULT_LEN);
+        return ERROR;
+      }
+      chan->capslimit = capslimit;
+    } else if (!strcmp(item[i], "colorlimit") || !strcmp(item[i], "color-limit")) {
+      i++;
+      if (i >= items) {
+        if (result)
+          strlcpy(result, "channel colorlimit needs argument", RESULT_LEN);
+        return ERROR;
+      }
+      chan->colorlimit = atoi(item[i]);
+   } else if (!strcmp(item[i], "closed-ban")) {
       i++;
       if (i >= items) {
         if (result)
@@ -1069,6 +1091,8 @@ int channel_add(char *result, const char *newname, char *options, bool isdefault
     chan->flood_nick_time = gfld_nick_time;
     chan->flood_mjoin_thr = 6;
     chan->flood_mjoin_time = 1;
+    chan->capslimit = 0;
+    chan->colorlimit = 0;
     chan->flood_mpub_thr = 10;
     chan->flood_mpub_time = 1;
     chan->flood_mbytes_thr = 500;

+ 2 - 0
src/mod/channels.mod/cmdschan.c

@@ -1242,6 +1242,8 @@ static void cmd_chaninfo(int idx, char *par)
     SHOW_INT("Exempt-time: ", chan->exempt_time, NULL, "Forever");
     SHOW_INT("Flood-exempt: ", chan->flood_exempt_mode, F_STR(chan->flood_exempt_mode), "None");
     SHOW_INT("Flood-lock-time: ", chan->flood_lock_time, NULL, "Don't");
+    SHOW_INT("Caps-Limit(%): ", chan->capslimit, NULL, "None");
+    SHOW_INT("Color-Limit: ", chan->colorlimit, NULL, "None");
     SHOW_INT("Invite-time: ", chan->invite_time, NULL, "Forever");
     SHOW_INT("Knock: ", chan->knock_flags, F_STR(chan->knock_flags), "None");
     SHOW_INT("Limit raise (limit): ", chan->limitraise, NULL, "Disabled");

+ 6 - 2
src/mod/channels.mod/userchan.c

@@ -718,7 +718,7 @@ chanmode { %s } groups { %s } bad-cookie %d manop %d mdop %d mop %d limit %d rev
 flood-chan %d:%d flood-bytes %d:%d flood-ctcp %d:%d flood-join %d:%d \
 flood-kick %d:%d flood-deop %d:%d flood-nick %d:%d flood-mjoin %d:%d \
 flood-mpub %d:%d flood-mbytes %d:%d flood-mctcp %d:%d \
-closed-ban %d closed-invite %d closed-private %d ban-time %d \
+capslimit %d colorlimit %d closed-ban %d closed-invite %d closed-private %d ban-time %d \
 exempt-time %d invite-time %d voice-non-ident %d auto-delay %d \
 flood-exempt %d flood-lock-time %d knock %d fish-key { %s } \
 %cmeankicks %cenforcebans %cdynamicbans %cuserbans %cbitch %cfloodban \
@@ -749,6 +749,8 @@ flood-exempt %d flood-lock-time %d knock %d fish-key { %s } \
 	chan->flood_mpub_thr, chan->flood_mpub_time,
 	chan->flood_mbytes_thr, chan->flood_mbytes_time,
 	chan->flood_mctcp_thr, chan->flood_mctcp_time,
+        chan->capslimit,
+        chan->colorlimit,
         chan->closed_ban,
 /* Chanint template
  *      chan->temp,
@@ -837,7 +839,7 @@ void write_chans_compat(bd::Stream& stream, int idx)
 bad-cookie %d manop %d mdop %d mop %d limit %d \
 flood-chan %d:%d flood-ctcp %d:%d flood-join %d:%d \
 flood-kick %d:%d flood-deop %d:%d flood-nick %d:%d \
-closed-ban %d closed-invite %d closed-private %d ban-time %d \
+capslimit %d colorlimit %d closed-ban %d closed-invite %d closed-private %d ban-time %d \
 exempt-time %d invite-time %d voice-non-ident %d auto-delay %d \
 %cenforcebans %cdynamicbans %cuserbans %cbitch \
 %cprivate %ccycle %cinactive %cdynamicexempts %cuserexempts \
@@ -858,6 +860,8 @@ exempt-time %d invite-time %d voice-non-ident %d auto-delay %d \
         chan->flood_kick_thr, chan->flood_kick_time,
         chan->flood_deop_thr, chan->flood_deop_time,
 	chan->flood_nick_thr, chan->flood_nick_time,
+        chan->capslimit,
+        chan->colorlimit,
         chan->closed_ban,
         chan->closed_invite,
         chan->closed_private,

+ 0 - 3
src/mod/irc.mod/chan.c

@@ -3326,10 +3326,8 @@ static int gotmsg(char *from, char *msg)
     int botmatch = 0;
     char *my_msg = NULL, *my_ptr = NULL, *fword = NULL;
 
-#ifdef unfinished
     if (me_op(chan) && doflood(chan))
       detect_offense(m, chan, msg);
-#endif
 
     if (m) {
       detect_chan_flood(m, from, chan, FLOOD_PRIVMSG);
@@ -3346,7 +3344,6 @@ static int gotmsg(char *from, char *msg)
       /* is it a cmd? */
       if (auth_prefix[0] && fword && fword[0] && fword[1] && ((botmatch && fword[0] != auth_prefix[0]) || (fword[0] == auth_prefix[0]))) {
         Auth *auth = Auth::Find(uhost);
-
         if (auth && auth->Authed()) {
           if (fword[0] == auth_prefix[0])
             fword++;

+ 76 - 21
src/mod/irc.mod/irc.c

@@ -61,6 +61,8 @@
 
 #include <stdarg.h>
 
+#include <math.h>
+
 #define PRIO_DEOP 1
 #define PRIO_KICK 2
 
@@ -112,39 +114,92 @@ voice_ok(memberlist *m, struct chanset_t *chan)
 #include "cmdsirc.c"
 #include "msgcmds.c"
 
-#ifdef unfinished
-static void
+static int
 detect_offense(memberlist* m, struct chanset_t *chan, char *msg)
 {
+  if (!chan || !msg
+      || !(chan->capslimit || chan->colorlimit)
+      || chan_sentkick(m)) //sanity check
+    return 0;
+
   struct flag_record fr = { FR_GLOBAL | FR_CHAN, 0, 0, 0 };
   struct userrec *u = m->user ? m->user : get_user_by_host(m->userhost);
-  int i = 0;
+  get_user_flagrec(u, &fr, chan->dname, chan);
 
-  //size_t tot = strlen(msg);
+  if (glob_bot(fr) ||
+      (m && chan->flood_exempt_mode == CHAN_FLAG_OP && chan_hasop(m)) ||
+      (m && chan->flood_exempt_mode == CHAN_FLAG_VOICE && (chan_hasvoice(m) || chan_hasop(m))))
+    return 0;
 
-  get_user_flagrec(u, &fr, chan->dname, chan);
+  int color_count = 0, hit_check = 0, hit_count = 0;
+  double caps_percentage = 0, caps_count = 0;
+  const double caps_limit = chan->capslimit ? double(chan->capslimit) / 100.0 : double(0);
+
+  // Need to know how long the message is, and want to ignore spaces, so avoid a strlen() and just loop to count
+  size_t tot = 0;
+  if (caps_limit) {
+    char *msg_check = msg;
+    while (msg_check && *msg_check) {
+      if (!egg_isspace(*msg_check) && *msg_check != 3 && *msg_check != 2) {
+        ++tot;
+      }
+      ++msg_check;
+    }
 
-  for (; *msg; ++msg) {
-    if (egg_isupper(*msg))
-      i++;
-  }
+    if (!tot) {
+      return 0;
+    }
 
-/*  if ((chan->capslimit)) { */
-  while (((msg) && *msg)) {
-    if (egg_isupper(*msg))
-      i++;
-    msg++;
+    if (tot >= 30) {
+      hit_check = tot/5; //check in-between for hits to save waste of cpu
+    }
   }
 
-/*
-  if (chan->capslimit && ((i / tot) >= chan->capslimit)) {
-dprintf(DP_MODE, "PRIVMSG %s :flood stats for %s: %d/%d are CAP, percentage: %d\n", chan->name, nick, i, tot, (i/tot)*100);
-  if ((((i / tot) * 100) >= 50)) {
-dprintf(DP_HELP, "PRIVMSG %s :cap flood.\n", chan->dname);
+  while (msg && *msg) {
+    // Skip spaces
+    if (egg_isspace(*msg)) {
+      ++msg;
+      continue;
+    }
+
+    if (egg_isupper(*msg)) {
+      ++caps_count;
+    } else if (*msg == 3 || *msg == 2) {
+      ++color_count;
+    }
+
+    if (hit_check && !(hit_count % hit_check)) {
+      if (caps_limit) {
+        caps_percentage = (caps_count)/(double(tot));
+        if (caps_percentage >= caps_limit) {
+          break;
+        }
+      }
+      if (chan->colorlimit && color_count >= chan->colorlimit) {
+        break;
+      }
+      ++hit_count;
+    }
+    ++msg;
   }
-*/
+  if (chan->capslimit && caps_count) {
+    caps_percentage = (caps_count)/(double(tot));
+    if (caps_percentage >= caps_limit) {
+      putlog(LOG_MODES, chan->name, "Caps flood (%d%%) from %s -- kicking", int(caps_percentage * 100), m->nick);
+      dprintf(DP_SERVER, "KICK %s %s :%s%s\n", chan->name, m->nick, kickprefix, response(RES_FLOOD));
+      m->flags |= SENTKICK;
+      return 1;
+    }
+  } else if (chan->colorlimit && color_count) {
+    if (color_count >= chan->colorlimit) {
+      putlog(LOG_MODES, chan->name, "Color flood (%d) from %s -- kicking", color_count, m->nick);
+      dprintf(DP_SERVER, "KICK %s %s :%s%s\n", chan->name, m->nick, kickprefix, response(RES_FLOOD));
+      m->flags |= SENTKICK;
+      return 1;
+    }
+  }
+  return 0;
 }
-#endif
 
 void unlock_chan(struct chanset_t *chan)
 {

+ 1 - 1
src/mod/irc.mod/irc.h

@@ -83,7 +83,7 @@ static void do_protect(struct chanset_t* chan, const char* reason);
 #define resetexempts(chan)  resetmasks((chan), (chan)->channel.exempt, (chan)->exempts, global_exempts, 'e')
 #define resetinvites(chan)  resetmasks((chan), (chan)->channel.invite, (chan)->invites, global_invites, 'I')
 
-//static void detect_offense(memberlist*, struct chanset_t *, char *);
+static int detect_offense(memberlist*, struct chanset_t *, char *);
 /* static int target_priority(struct chanset_t *, memberlist *, int); */
 static bool do_op(char *, struct chanset_t *, bool, bool);
 static void request_op(struct chanset_t *);