Просмотр исходного кода

Merge branch '37-drone-floods'

* 37-drone-floods:
  Show what kind of mass flood was detected (#37)
  Fix ctcp flood getting attributed to 'pub'
  Remove unneeded chanset 'nomassjoin' (#37)
  Add chansets 'flood-mchan', 'flood-mbytes', 'flood-mctcp' to control reactions to mass floods (#37)
  Track mass floods by their flood-m* settings
  Track all clients to detect mass flooding (#37)
  Only process drone flood if opped (#37)
  Lockdown channel (+im) if banlist becomes full (#37)
  Use enum for reason for locking chan (#37)

Conflicts:
	doc/UPDATES
Bryan Drewery 14 лет назад
Родитель
Сommit
1941d21b82

+ 4 - 0
doc/UPDATES

@@ -39,6 +39,10 @@
   * Add 'chanset revenge' which will react/kick/ban/remove users who kick/ban/deop bots.
   * Fix flood kicking not properly tracking multiple clients at once (#43)
   * Add chanset 'flood-bytes' to count how many bytes:second a user sends before getting kicked for flood. (#42)
+  * Bot will now lockdown channel (+im) if banlist becomes full (#37)
+  * 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'
 
 1.3.4 - http://wraith.botpack.net/milestone/1.3.4
   * Fix various compile warnings with newer GCC

+ 13 - 4
doc/help.txt

@@ -474,9 +474,6 @@ See also: link%{+a}, newhub%{-}
                        the bot from fighting with services such as ChanServ, or
                        from kicking IRCops when setting channel modes without
                        having ops.
-        $bnomassjoin$b     If more than 6 clients join in 1 second, set +im
-                           for 2 minutes. (Could be triggered from net-split
-                           rejoin if clients hop servers and change nicks)
         $bprivate$b        This by far is probably the single most unique and
                        important feature of this pack. With this set, users with
                        global +o will not implicitly have access to the channel.
@@ -559,8 +556,20 @@ See also: link%{+a}, newhub%{-}
         $bflood-nick$b   Set here how many nick changes in how many seconds from
                     one host constitutes a flood. Setting this to 0 or 0:0
                     disables nick flood protection for the channel.
+   The following settings will trigger a lockdown (chanmode +mi) for 'flood-lock-time'
+   seconds.
         $bflood-mjoin$b  Set how many joins in how many seconds before triggering
-                    a lockdown for mass join. ($bnomassjoin$b must be set).
+                    a lockdown for mass join. Setting this to 0 or 0:0 disables mass
+                    join protection for the channel.
+        $bflood-mchan$b  Set here how many channel messages in how many seconds
+                    from any host constitutes a flood. Setting this to 0 or 0:0
+                    disables text flood protection for the channel.
+        $bflood-mbytes$b  Set here how many bytes in how many seconds from any
+                    host constitutes a flood. Setting this to 0 or 0:0
+                    disables text flood protection for the channel.
+        $bflood-mctcp$b  Set here how many channel ctcps in how many seconds from
+                    any host constitutes a flood. Setting this to 0 or 0:0
+                    disables ctcp flood protection for the channel.
    These can all be changed simultaneously with $bflood-*$b
  
 See also: %{-}%{+n}+chan, -chan%{-}%{+m|m}, chanset%{-}

+ 7 - 2
src/chan.h

@@ -168,8 +168,12 @@ struct chanset_t {
   uint32_t ircnet_status;
   int flood_pub_thr;
   interval_t flood_pub_time;
+  int flood_mpub_thr;
+  interval_t flood_mpub_time;
   int flood_bytes_thr;
   interval_t flood_bytes_time;
+  int flood_mbytes_thr;
+  interval_t flood_mbytes_time;
   int flood_join_thr;
   interval_t flood_join_time;
   int flood_deop_thr;
@@ -178,6 +182,8 @@ struct chanset_t {
   interval_t flood_kick_time;
   int flood_ctcp_thr;
   interval_t flood_ctcp_time;
+  int flood_mctcp_thr;
+  interval_t flood_mctcp_time;
   int flood_nick_thr;
   interval_t flood_nick_time;
   interval_t flood_lock_time;
@@ -253,7 +259,7 @@ struct chanset_t {
 #define CHAN_CYCLE          BIT11	/* cycle the channel if possible      */
 #define CHAN_INACTIVE       BIT12	/* no irc support for this channel */
 #define CHAN_VOICE          BIT13	/* a bot +y|y will voice *, except +q */
-#define CHAN_NOMASSJOIN     BIT14       /* watch for mass join for flood nets and react */
+//#define CHAN_NOMASSJOIN     BIT14       /* watch for mass join for flood nets and react */
 #define CHAN_NODESYNCH      BIT15
 #define CHAN_FASTOP         BIT16	/* Bots will not use +o-b to op (no cookies) */ 
 #define CHAN_PRIVATE        BIT17	/* users need |o to access chan */ 
@@ -317,7 +323,6 @@ struct chanset_t *findchan_by_dname(const char *name);
 #define channel_fastop(chan) (chan->status & CHAN_FASTOP)
 #define channel_privchan(chan) (chan->status & CHAN_PRIVATE)
 #define channel_autoop(chan) (chan->status & CHAN_AUTOOP)
-#define channel_nomassjoin(chan) (chan->status & CHAN_NOMASSJOIN)
 #define channel_meankicks(chan) (chan->status & CHAN_MEANKICKS)
 #define channel_rbl(chan) (chan->status & CHAN_RBL)
 #define channel_voicebitch(chan) (chan->status & CHAN_VOICEBITCH)

+ 23 - 4
src/mod/channels.mod/chanmisc.c

@@ -663,10 +663,6 @@ int channel_modify(char *result, struct chanset_t *chan, int items, char **item,
       chan->status |= CHAN_BOTBITCH;
     else if (!strcmp(item[i], "-botbitch"))
       chan->status &= ~CHAN_BOTBITCH;
-    else if (!strcmp(item[i], "+nomassjoin"))
-      chan->status |= CHAN_NOMASSJOIN;
-    else if (!strcmp(item[i], "-nomassjoin"))
-      chan->status &= ~CHAN_NOMASSJOIN;
     else if (!strcmp(item[i], "+meankicks"))
       chan->status |= CHAN_MEANKICKS;
     else if (!strcmp(item[i], "-meankicks"))
@@ -709,6 +705,8 @@ int channel_modify(char *result, struct chanset_t *chan, int items, char **item,
     else if (!cmd && !HAVE_TAKE && !strcmp(item[i], "-take")) ;
     else if (!cmd && !strcmp(item[i], "stopnethack-mode")) ;
     else if (!cmd && !strcmp(item[i], "revenge-mode")) ;
+    else if (!cmd && !strcmp(item[i], "+nomassjoin")) ;
+    else if (!cmd && !strcmp(item[i], "-nomassjoin")) ;
     else if (!cmd && !strcmp(item[i], "+revenge")) ;
     else if (!cmd && !strcmp(item[i], "-revenge")) ;
     else if (!cmd && !strcmp(item[i], "+revengebot")) ;
@@ -767,12 +765,18 @@ int channel_modify(char *result, struct chanset_t *chan, int items, char **item,
 
         chan->flood_pub_thr = thr;
         chan->flood_pub_time = time;
+        chan->flood_mpub_thr = thr;
+        chan->flood_mpub_time = time;
         chan->flood_bytes_thr = thr;
         chan->flood_bytes_time = time;
+        chan->flood_mbytes_thr = thr;
+        chan->flood_mbytes_time = time;
         chan->flood_join_thr = thr;
         chan->flood_join_time = time;
         chan->flood_ctcp_thr = thr;
         chan->flood_ctcp_time = time;
+        chan->flood_mctcp_thr = thr;
+        chan->flood_mctcp_time = time;
         chan->flood_kick_thr = thr;
         chan->flood_kick_time = time;
         chan->flood_deop_thr = thr;
@@ -805,6 +809,15 @@ int channel_modify(char *result, struct chanset_t *chan, int items, char **item,
       } else if (!strcmp(item[i] + 6, "mjoin")) {
 	pthr = &chan->flood_mjoin_thr;
 	ptime = &chan->flood_mjoin_time;
+      } else if (!strcmp(item[i] + 6, "mpub")) {
+	pthr = &chan->flood_mpub_thr;
+	ptime = &chan->flood_mpub_time;
+      } else if (!strcmp(item[i] + 6, "mbytes")) {
+	pthr = &chan->flood_mbytes_thr;
+	ptime = &chan->flood_mbytes_time;
+      } else if (!strcmp(item[i] + 6, "mctcp")) {
+	pthr = &chan->flood_mctcp_thr;
+	ptime = &chan->flood_mctcp_time;
       } else { /* Ignore for optimal forward compatibility */
         i++;
         continue;
@@ -1056,6 +1069,12 @@ 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->flood_mpub_thr = 10;
+    chan->flood_mpub_time = 1;
+    chan->flood_mbytes_thr = 500;
+    chan->flood_mbytes_time = 1;
+    chan->flood_mctcp_thr = 7;
+    chan->flood_mctcp_time = 1;
     chan->limitraise = 20;
     chan->ban_time = global_ban_time;
     chan->exempt_time = global_exempt_time;

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

@@ -879,8 +879,6 @@ void channels_report(int idx, int details)
  *	if (channel_temp(chan))
  *	  i += my_strcpy(s + i, "temp ");
 */
-        if (channel_nomassjoin(chan))
-          i += my_strcpy(s + i, "nomassjoin ");
         if (channel_botbitch(chan))
           i += my_strcpy(s + i, "botbitch ");
         if (channel_backup(chan))

+ 7 - 6
src/mod/channels.mod/cmdschan.c

@@ -1204,7 +1204,6 @@ static void cmd_chaninfo(int idx, char *par)
     SHOW_FLAG("inactive",	channel_inactive(chan));
     SHOW_FLAG("meankicks",	channel_meankicks(chan));
     SHOW_FLAG("nodesynch",	channel_nodesynch(chan));
-    SHOW_FLAG("nomassjoin",	channel_nomassjoin(chan));
     SHOW_FLAG("private",	channel_privchan(chan));
     SHOW_FLAG("protect",	channel_protect(chan));
     SHOW_FLAG("rbl",		channel_rbl(chan));
@@ -1257,17 +1256,19 @@ static void cmd_chaninfo(int idx, char *par)
     SHOW_INT("Protect-backup: ", chan->protect_backup, "Do!", "Don't!");
     SHOW_INT("Voice-non-ident: ", chan->voice_non_ident, "Do!", "Don't!");
 
-    dprintf(idx, "Flood settings:   chan bytes ctcp join kick deop nick mjoin\n");
-    dprintf(idx, "  number:          %3d  %4d  %3d  %3d  %3d  %3d  %3d  %3d\n",
+    dprintf(idx, "Flood settings:   chan bytes ctcp join kick deop nick mjoin mpub mbytes mctcp\n");
+    dprintf(idx, "  number:          %3d  %4d  %3d  %3d  %3d  %3d  %3d   %3d  %3d   %4d   %3d\n",
 	    chan->flood_pub_thr, chan->flood_bytes_thr, chan->flood_ctcp_thr,
 	    chan->flood_join_thr, chan->flood_kick_thr,
 	    chan->flood_deop_thr, chan->flood_nick_thr,
-            chan->flood_mjoin_thr);
-    dprintf(idx, "  time  :          %3u  %4u  %3u  %3u  %3u  %3u  %3u  %3u\n",
+            chan->flood_mjoin_thr, chan->flood_mpub_thr,
+            chan->flood_mbytes_thr, chan->flood_mctcp_thr);
+    dprintf(idx, "  time  :          %3u  %4u  %3u  %3u  %3u  %3u  %3u   %3u  %3u   %4u  %4u\n",
 	    chan->flood_pub_time, chan->flood_bytes_time, chan->flood_ctcp_time,
 	    chan->flood_join_time, chan->flood_kick_time,
 	    chan->flood_deop_time, chan->flood_nick_time,
-            chan->flood_mjoin_time);
+            chan->flood_mjoin_time, chan->flood_mpub_time,
+            chan->flood_mbytes_time, chan->flood_mctcp_time);
   }
 }
 

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

@@ -717,13 +717,14 @@ bd::String channel_to_string(struct chanset_t* chan, bool force_inactive) {
 chanmode { %s } groups { %s } bad-cookie %d manop %d mdop %d mop %d limit %d revenge %d ban-type %d \
 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 \
 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 \
 %cprivate %ccycle %cinactive %cdynamicexempts %cuserexempts \
 %cdynamicinvites %cuserinvites %cnodesynch %cclosed %cvoice \
-%cfastop %cautoop %cbotbitch %cbackup %cnomassjoin %crbl %cvoicebitch %cprotect protect-backup %d %c%s",
+%cfastop %cautoop %cbotbitch %cbackup %crbl %cvoicebitch %cprotect protect-backup %d %c%s",
 	w,
 /* Chanchar template
  *      temp,
@@ -745,6 +746,9 @@ flood-exempt %d flood-lock-time %d knock %d fish-key { %s } \
         chan->flood_deop_thr, chan->flood_deop_time,
 	chan->flood_nick_thr, chan->flood_nick_time,
 	chan->flood_mjoin_thr, chan->flood_mjoin_time,
+	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->closed_ban,
 /* Chanint template
  *      chan->temp,
@@ -781,7 +785,6 @@ flood-exempt %d flood-lock-time %d knock %d fish-key { %s } \
         PLSMNS(channel_autoop(chan)),
         PLSMNS(channel_botbitch(chan)),
         PLSMNS(channel_backup(chan)),
-        PLSMNS(channel_nomassjoin(chan)),
         PLSMNS(channel_rbl(chan)),
         PLSMNS(channel_voicebitch(chan)),
         PLSMNS(channel_protect(chan)),
@@ -839,7 +842,7 @@ exempt-time %d invite-time %d voice-non-ident %d auto-delay %d \
 %cenforcebans %cdynamicbans %cuserbans %cbitch \
 %cprivate %ccycle %cinactive %cdynamicexempts %cuserexempts \
 %cdynamicinvites %cuserinvites %cnodesynch %cclosed %cvoice \
-%cfastop %cautoop %cbotbitch %cbackup %cnomassjoin %c%s}\n",
+%cfastop %cautoop %cbotbitch %cbackup %c%s}\n",
 	chan->dname,
 	w,
         chan->added_by,
@@ -881,7 +884,6 @@ exempt-time %d invite-time %d voice-non-ident %d auto-delay %d \
         PLSMNS(channel_autoop(chan)),
         PLSMNS(channel_botbitch(chan)),
         PLSMNS(channel_backup(chan)),
-        PLSMNS(channel_nomassjoin(chan)),
 	HAVE_TAKE ? PLSMNS(channel_take(chan)) : ' ',
         HAVE_TAKE ? "take " : " "
     );

+ 77 - 7
src/mod/irc.mod/chan.c

@@ -575,9 +575,9 @@ static bool detect_chan_flood(memberlist* m, const char *from, struct chanset_t
     return 0;
 
   char h[UHOSTLEN] = "", ftype[14] = "", *p = NULL;
-  int thr = 0;
+  int thr = 0, mthr = 0;
   int increment = 1;
-  time_t lapse = 0;
+  time_t lapse = 0, mlapse = 0;
 
   /* Determine how many are necessary to make a flood. */
   switch (which) {
@@ -585,18 +585,24 @@ static bool detect_chan_flood(memberlist* m, const char *from, struct chanset_t
   case FLOOD_NOTICE:
     thr = chan->flood_pub_thr;
     lapse = chan->flood_pub_time;
+    mthr = chan->flood_mpub_thr;
+    mlapse = chan->flood_mpub_time;
     strlcpy(ftype, "pub", sizeof(ftype));
     break;
   case FLOOD_BYTES:
     thr = chan->flood_bytes_thr;
     lapse = chan->flood_bytes_time;
+    mthr = chan->flood_mbytes_thr;
+    mlapse = chan->flood_mbytes_time;
     strlcpy(ftype, "bytes", sizeof(ftype));
     increment = static_cast<int>(strlen(msg));
     break;
   case FLOOD_CTCP:
     thr = chan->flood_ctcp_thr;
     lapse = chan->flood_ctcp_time;
-    strlcpy(ftype, "pub", sizeof(ftype));
+    mthr = chan->flood_mctcp_thr;
+    mlapse = chan->flood_mctcp_time;
+    strlcpy(ftype, "ctcp", sizeof(ftype));
     break;
   case FLOOD_NICK:
     thr = chan->flood_nick_thr;
@@ -620,8 +626,6 @@ static bool detect_chan_flood(memberlist* m, const char *from, struct chanset_t
     strlcpy(ftype, "kick", sizeof(ftype));
     break;
   }
-  if ((thr == 0) || (lapse == 0))
-    return 0;			/* no flood protection */
 
   if ((which == FLOOD_KICK) || (which == FLOOD_DEOP))
     p = m->nick;
@@ -634,6 +638,40 @@ static bool detect_chan_flood(memberlist* m, const char *from, struct chanset_t
       return 0;
   }
 
+  // Track across all clients in the channel
+  if (mlapse && mthr) {
+    if (!chan->channel.floodtime->contains("all")) {
+      (*chan->channel.floodtime)["all"][which] = now;
+      (*chan->channel.floodnum)["all"][which] = increment;
+      return 0;
+    }
+
+    bd::HashTable<flood_t, time_t>      *global_floodtime = &(*chan->channel.floodtime)["all"];
+    bd::HashTable<flood_t, int>         *global_floodnum = &(*chan->channel.floodnum)["all"];
+
+    if ((*global_floodtime)[which] < now - mlapse) {
+      /* Flood timer expired, reset it */
+      (*global_floodtime)[which] = now;
+      (*global_floodnum)[which] = increment;
+    } else {
+      (*global_floodnum)[which] += increment;
+
+      if ((*global_floodnum)[which] >= mthr) {	/* FLOOD */
+        /* Reset counters */
+        (*global_floodnum).remove(which);
+        (*global_floodtime).remove(which);
+        if (!chan->channel.drone_set_mode) {
+          lockdown_chan(chan, FLOOD_MASS_FLOOD, ftype);
+        }
+      }
+    }
+  }
+
+  if ((thr == 0) || (lapse == 0)) {
+    return 0;			/* no flood protection */
+  }
+
+  // Track individual hosts/clients
   bd::HashTable<flood_t, time_t>      *floodtime; // floodtime[FLOOD_PRIVMSG] = now;
   bd::HashTable<flood_t, int>         *floodnum;  //  floodnum[FLOOD_PRIVMSG] = 1;
 
@@ -2446,6 +2484,37 @@ static int got475(char *from, char *msg)
   return 0;
 }
 
+/* got 478: Channel ban list is full
+ * [@] irc.blessed.net 478 wtest #wraith-devel *!*@host.com :Channel ban list is full
+ */
+static int got478(char *from, char *msg)
+{
+  char *chname = NULL;
+  struct chanset_t *chan = NULL;
+
+  newsplit(&msg);
+  chname = newsplit(&msg);
+
+  /* !channel short names (also referred to as 'description names'
+   * can be received by skipping over the unique ID.
+   */
+  if ((chname[0] == '!') && (strlen(chname) > CHANNEL_ID_LEN)) {
+    chname += CHANNEL_ID_LEN;
+    chname[0] = '!';
+  }
+  /* We use dname because name is first set on JOIN and we might not
+   * have joined the channel yet.
+   */
+  chan = findchan_by_dname(chname);
+  if (chan && shouldjoin(chan)) {
+    // Only lockdown if not already locked down
+    if (!chan->channel.drone_set_mode) {
+      lockdown_chan(chan, FLOOD_BANLIST);
+    }
+  }
+  return 0;
+}
+
 /* got invitation
  */
 static int gotinvite(char *from, char *msg)
@@ -2706,7 +2775,7 @@ static int gotjoin(char *from, char *chname)
         bool is_op = chk_op(fr, chan);
 
         /* Check for a mass join */
-        if (!splitjoin && channel_nomassjoin(chan) && !is_op) {
+        if (!splitjoin && chan->flood_mjoin_time && chan->flood_mjoin_thr && !is_op) {
           if (chan->channel.drone_jointime < now - chan->flood_mjoin_time) {      //expired, reset counter
             chan->channel.drone_joins = 0;
           }
@@ -2714,7 +2783,7 @@ static int gotjoin(char *from, char *chname)
           chan->channel.drone_jointime = now;
 
           if (!chan->channel.drone_set_mode && chan->channel.drone_joins >= chan->flood_mjoin_thr) {  //flood from dronenet, let's attempt to set +im
-            detected_drone_flood(chan, m);
+            lockdown_chan(chan, FLOOD_MASSJOIN);
           }
         }
 
@@ -3411,6 +3480,7 @@ static cmd_t irc_raw[] =
   {"473",	"",	(Function) got473,	"irc:473", LEAF},
   {"474",	"",	(Function) got474,	"irc:474", LEAF},
   {"475",	"",	(Function) got475,	"irc:475", LEAF},
+  {"478",	"",	(Function) got478,	"irc:478", LEAF},
   {"INVITE",	"",	(Function) gotinvite,	"irc:invite", LEAF},
   {"TOPIC",	"",	(Function) gottopic,	"irc:topic", LEAF},
   {"331",	"",	(Function) got331,	"irc:331", LEAF},

+ 11 - 2
src/mod/irc.mod/irc.c

@@ -160,7 +160,10 @@ void unlock_chan(struct chanset_t *chan)
   chan->channel.drone_set_mode = 0;
 }
 
-void detected_drone_flood(struct chanset_t* chan, memberlist* m) {
+void lockdown_chan(struct chanset_t* chan, flood_reason_t reason, const char* flood_type) {
+  if (!me_op(chan)) {
+    return;
+  }
   egg_timeval_t howlong;
 
   chan->channel.drone_set_mode = 0;
@@ -186,7 +189,13 @@ void detected_drone_flood(struct chanset_t* chan, memberlist* m) {
     howlong.usec = 0;
     timer_create_complex(&howlong, "unlock", (Function) unlock_chan, (void *) chan, 0);
 
-    putlog(LOG_MISC, "*", "Flood detected in %s! Locking for %d seconds.", chan->dname, chan->flood_lock_time);
+    if (reason == FLOOD_MASSJOIN) {
+      putlog(LOG_MISC, "*", "Join flood detected in %s! Locking for %d seconds.", chan->dname, chan->flood_lock_time);
+    } else if (reason == FLOOD_BANLIST) {
+      putlog(LOG_MISC, "*", "Banlist full in %s! Locking for %d seconds.", chan->dname, chan->flood_lock_time);
+    } else if (reason == FLOOD_MASS_FLOOD) {
+      putlog(LOG_MISC, "*", "Mass flood (%s) detected in %s! Locking for %d seconds.", flood_type, chan->dname, chan->flood_lock_time);
+    }
   }
 }
 

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

@@ -24,6 +24,12 @@ namespace bd {
   class String;
 }
 
+enum flood_reason_t {
+  FLOOD_MASSJOIN,
+  FLOOD_BANLIST,
+  FLOOD_MASS_FLOOD
+};
+
 #ifdef CACHE
 typedef struct cache_chan_b {
   struct cache_chan_b *next;
@@ -90,7 +96,7 @@ static bool killmember(struct chanset_t *chan, char *nick);
 static void check_lonely_channel(struct chanset_t *chan);
 static int gotmode(char *, char *);
 void unset_im(struct chanset_t* chan);
-void detected_drone_flood(struct chanset_t* chan, memberlist*);
+void lockdown_chan(struct chanset_t* chan, flood_reason_t reason, const char* flood_type = NULL);
 static void send_chan_who(int queue, struct chanset_t* chan, bool chain = 0);
 #define newban(chan, mask, who)         new_mask((chan)->channel.ban, mask, who)
 #define newexempt(chan, mask, who)      new_mask((chan)->channel.exempt, mask, who)