Преглед изворни кода

Merge branch 'protect'

* protect:
  * Fix typo
  * Don't enforce +protect/mdop/mop against servers (netsplit bursts)
  * If reversing modes, dump them out quickly
  * Don't count bot actions as fighting
  * fight-threshold is now a +protect trigger
  * Don't enforce_bitch() on the trigger.
  * When enforcing +protect, reverse the modes which triggered it.
  * React to non-users for 'mop/mdop' and +protect
  * Implement protect-backup
  * Add chanset 'protect-backup' which will optionally set +backup in the event of a takeover with +protect.
  * Implement +protect
  * Add chanset '+/-protect' which will set +botbitch and mass deop if any takeover is detected.

Conflicts:
	doc/help.txt
	src/chan.h
	src/mod/channels.mod/channels.c
	src/mod/channels.mod/tclchan.c
	src/mod/channels.mod/userchan.c
	src/mod/irc.mod/irc.h
	src/mod/irc.mod/mode.c
Bryan Drewery пре 16 година
родитељ
комит
cd3397ecb5

+ 4 - 0
doc/UPDATES

@@ -111,6 +111,10 @@
   * Credits/cmd_about updates.
   * Credits/cmd_about updates.
   * Add chanset '+/-voicebitch' for a +bitch style enforcement over voices.
   * Add chanset '+/-voicebitch' for a +bitch style enforcement over voices.
   * Add cmd_netmsg back in. It may only be used to message channels the bots are opped in.
   * Add cmd_netmsg back in. It may only be used to message channels the bots are opped in.
+  * Add chanset '+/-protect' which will set +botbitch and mass deop if any takeover is detected.
+  * Add chanset 'protect-backup' which will optionally set +backup in the event of a takeover with +protect.
+  * Channel settings 'mop' and 'mdop' now react to non-users.
+  * set 'fight-threshold' now requires +protect and is a trigger for +protect. See 'help chanset' for +protect info.
 
 
 1.2.16.1
 1.2.16.1
 * Fix linux compile errors
 * Fix linux compile errors

+ 9 - 3
doc/help.txt

@@ -355,7 +355,7 @@ See also: version, netversion, about%{+m|m}, status%{-}
 %{-}
 %{-}
  
  
 See also: link
 See also: link
-::chaninfo:
+::chaninfo
 ###  $bchaninfo$b <channel>
 ###  $bchaninfo$b <channel>
    This lists all the settings for the bot on the given channel.
    This lists all the settings for the bot on the given channel.
    It shows any of the following:
    It shows any of the following:
@@ -427,6 +427,8 @@ See also: link
                           channel is set to +i. Once the channel is -i then the
                           channel is set to +i. Once the channel is -i then the
                           invite will be removed the next time the bot checks.
                           invite will be removed the next time the bot checks.
                           Please note that this is an IRCnet feature.
                           Please note that this is an IRCnet feature.
+        $bprotect-backup$b    Set +backup in the event of a takeover if the
+                          channel is +protect.
         $bvoice-non-ident$b   If channel is +voice, clients without an ident will 
         $bvoice-non-ident$b   If channel is +voice, clients without an ident will 
                           be voiced. Set to 0 to not voice clients without ident.
                           be voiced. Set to 0 to not voice clients without ident.
  
  
@@ -486,6 +488,9 @@ See also: link
                        Users who lose access to a channel because of this setting
                        Users who lose access to a channel because of this setting
                        will see no reference to it over the botnet in any place.
                        will see no reference to it over the botnet in any place.
                        The +o restriction goes for +v as well.
                        The +o restriction goes for +v as well.
+        $bprotect$b        Set chan +botbitch and mass deop the channel if someone
+                       attempts to takeover. The channel is also set +backup
+                       if protect-backup is set.
         $brbl$b             Do RBL lookups on clients and ban on matches.
         $brbl$b             Do RBL lookups on clients and ban on matches.
                        Uses 'rbl-servers'. See also '%dhelp set'. Only the
                        Uses 'rbl-servers'. See also '%dhelp set'. Only the
                        +r bot will do lookups. On positive match, the bot
                        +r bot will do lookups. On positive match, the bot
@@ -1680,8 +1685,9 @@ See also: reload, backup
 [N]  $blag-threshold$b   Maximum acceptable server lag for the bot to send/honor requests.
 [N]  $blag-threshold$b   Maximum acceptable server lag for the bot to send/honor requests.
 [N]  $bkill-threshold$b  When more than this many bots have been killed/klined in the last
 [N]  $bkill-threshold$b  When more than this many bots have been killed/klined in the last
                           minute, close all channels.
                           minute, close all channels.
-[N]  $bfight-threshold$b When more than this many +ob/-ob/kicks have happened on a channel in 
-                          1 minute, the channel is closed.
+[N]  $bfight-threshold$b When more than this many +obeI/-obeI/kicks have happened on a channel
+                          in 1 minute, the channel is locked down. Channel must be +protect.
+                          See also '%dhelp chanset' for +protect.
  
  
 [N]  $bcloak-script$b    Decides which script the bot cloaks as. 
 [N]  $bcloak-script$b    Decides which script the bot cloaks as. 
                           If set to 0, a random script will be used.
                           If set to 0, a random script will be used.

+ 3 - 1
src/chan.h

@@ -179,6 +179,7 @@ struct chanset_t {
   int ban_type;
   int ban_type;
   interval_t auto_delay;
   interval_t auto_delay;
   int knock_flags;
   int knock_flags;
+  int protect_backup;
 /* Chanint template 
 /* Chanint template 
  *int temp;
  *int temp;
  */
  */
@@ -230,7 +231,7 @@ struct chanset_t {
 #define CHAN_CLOSED         BIT3	/* Only users +o can join */
 #define CHAN_CLOSED         BIT3	/* Only users +o can join */
 #define CHAN_BITCH          BIT4	/* be a tightwad with ops             */
 #define CHAN_BITCH          BIT4	/* be a tightwad with ops             */
 #define CHAN_TAKE 	    BIT5	/* When a bot gets opped, take the chan */
 #define CHAN_TAKE 	    BIT5	/* When a bot gets opped, take the chan */
-//#define CHAN_KNOCK 	    BIT6	/* Auto invite qualifying people when they knock */
+#define CHAN_PROTECT 	    BIT6	/* Go +bitch and mass deop if anyone mass ops or mass deops */
 #define CHAN_BOTBITCH       BIT7        /* only let bots be opped? */
 #define CHAN_BOTBITCH       BIT7        /* only let bots be opped? */
 #define CHAN_BACKUP         BIT8	/* Join the BOT_BACKUP bots when set */
 #define CHAN_BACKUP         BIT8	/* Join the BOT_BACKUP bots when set */
 #define CHAN_SECRET         BIT9	/* don't advertise channel on botnet  */
 #define CHAN_SECRET         BIT9	/* don't advertise channel on botnet  */
@@ -310,6 +311,7 @@ struct chanset_t *findchan_by_dname(const char *name);
 #define channel_meankicks(chan) (chan->status & CHAN_MEANKICKS)
 #define channel_meankicks(chan) (chan->status & CHAN_MEANKICKS)
 #define channel_rbl(chan) (chan->status & CHAN_RBL)
 #define channel_rbl(chan) (chan->status & CHAN_RBL)
 #define channel_voicebitch(chan) (chan->status & CHAN_VOICEBITCH)
 #define channel_voicebitch(chan) (chan->status & CHAN_VOICEBITCH)
+#define channel_protect(chan) (chan->status & CHAN_PROTECT)
 /* Chanflag template
 /* Chanflag template
  *#define channel_temp(chan) (chan->status & CHAN_PRIVATE)
  *#define channel_temp(chan) (chan->status & CHAN_PRIVATE)
  */
  */

+ 1 - 1
src/chanprog.c

@@ -54,7 +54,7 @@
 #endif
 #endif
 #include <sys/utsname.h>
 #include <sys/utsname.h>
 
 
-char *def_chanset = "+enforcebans +dynamicbans +userbans -bitch +cycle -inactive +userexempts -dynamicexempts +userinvites -dynamicinvites -nodesynch -closed -take -voice -private -fastop +meankicks ban-type 3";
+char *def_chanset = "+enforcebans +dynamicbans +userbans -bitch +cycle -inactive +userexempts -dynamicexempts +userinvites -dynamicinvites -nodesynch -closed -take -voice -private -fastop +meankicks ban-type 3 protect-backup 1";
 struct chanset_t 	*chanset = NULL;	/* Channel list			*/
 struct chanset_t 	*chanset = NULL;	/* Channel list			*/
 struct chanset_t	*chanset_default = NULL;	/* Default channel list */
 struct chanset_t	*chanset_default = NULL;	/* Default channel list */
 char 			admin[121] = "";	/* Admin info			*/
 char 			admin[121] = "";	/* Admin info			*/

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

@@ -885,6 +885,8 @@ void channels_report(int idx, int details)
           i += my_strcpy(s + i, "rbl ");
           i += my_strcpy(s + i, "rbl ");
         if (channel_voicebitch(chan))
         if (channel_voicebitch(chan))
           i += my_strcpy(s + i, "voicebitch ");
           i += my_strcpy(s + i, "voicebitch ");
+        if (channel_protect(chan))
+          i += my_strcpy(s + i, "protect ");
 /* Chanflag template
 /* Chanflag template
  *	if (channel_temp(chan))
  *	if (channel_temp(chan))
  *	  i += my_strcpy(s + i, "temp ");
  *	  i += my_strcpy(s + i, "temp ");

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

@@ -1249,6 +1249,7 @@ static void cmd_chaninfo(int idx, char *par)
     SHOW_FLAG("nodesynch",	channel_nodesynch(chan));
     SHOW_FLAG("nodesynch",	channel_nodesynch(chan));
     SHOW_FLAG("nomassjoin",	channel_nomassjoin(chan));
     SHOW_FLAG("nomassjoin",	channel_nomassjoin(chan));
     SHOW_FLAG("private",	channel_privchan(chan));
     SHOW_FLAG("private",	channel_privchan(chan));
+    SHOW_FLAG("protect",	channel_protect(chan));
     SHOW_FLAG("rbl",		channel_rbl(chan));
     SHOW_FLAG("rbl",		channel_rbl(chan));
 //    SHOW_FLAG("revengebot",	channel_revengebot(chan));
 //    SHOW_FLAG("revengebot",	channel_revengebot(chan));
 //    SHOW_FLAG("revenge",	channel_revenge(chan));
 //    SHOW_FLAG("revenge",	channel_revenge(chan));
@@ -1297,6 +1298,7 @@ static void cmd_chaninfo(int idx, char *par)
     deflag = chan->mop;
     deflag = chan->mop;
     SHOW_INT("Mop: ", chan->mop, P_STR, "Ignore");
     SHOW_INT("Mop: ", chan->mop, P_STR, "Ignore");
 //    SHOW_INT("Revenge-mode: ", chan->revenge_mode, NULL, NULL);
 //    SHOW_INT("Revenge-mode: ", chan->revenge_mode, NULL, NULL);
+    SHOW_INT("Protect-backup: ", chan->protect_backup, "Do!", "Don't!");
     SHOW_INT("Voice-non-ident: ", chan->voice_non_ident, "Do!", "Don't!");
     SHOW_INT("Voice-non-ident: ", chan->voice_non_ident, "Do!", "Don't!");
 
 
     dprintf(idx, "Flood settings:   chan ctcp join kick deop nick mjoin\n");
     dprintf(idx, "Flood settings:   chan ctcp join kick deop nick mjoin\n");

+ 13 - 0
src/mod/channels.mod/tclchan.c

@@ -543,6 +543,14 @@ int channel_modify(char *result, struct chanset_t *chan, int items, char **item,
         return ERROR;
         return ERROR;
       }
       }
       chan->ban_type = atoi(item[i]);
       chan->ban_type = atoi(item[i]);
+    } else if (!strcmp(item[i], "protect-backup")) {
+      i++;
+      if (i >= items) {
+        if (result)
+          strlcpy(result, "channel protect-backup needs argument", RESULT_LEN);
+        return ERROR;
+      }
+      chan->protect_backup = atoi(item[i]);
      
      
 
 
 /* Chanint template
 /* Chanint template
@@ -646,6 +654,10 @@ int channel_modify(char *result, struct chanset_t *chan, int items, char **item,
       chan->status |= CHAN_VOICEBITCH;
       chan->status |= CHAN_VOICEBITCH;
     else if (!strcmp(item[i], "-voicebitch"))
     else if (!strcmp(item[i], "-voicebitch"))
       chan->status &= ~CHAN_VOICEBITCH;
       chan->status &= ~CHAN_VOICEBITCH;
+    else if (!strcmp(item[i], "+protect"))
+      chan->status |= CHAN_PROTECT;
+    else if (!strcmp(item[i], "-protect"))
+      chan->status &= ~CHAN_PROTECT;
 /* Chanflag template
 /* Chanflag template
  *  else if (!strcmp(item[i], "+temp"))
  *  else if (!strcmp(item[i], "+temp"))
  *    chan->status |= CHAN_TEMP;
  *    chan->status |= CHAN_TEMP;
@@ -975,6 +987,7 @@ int channel_add(char *result, char *newname, char *options, bool isdefault)
 /* Chanint template
 /* Chanint template
  *  chan->temp = 0;
  *  chan->temp = 0;
  */
  */
+    chan->protect_backup = 1;
     chan->knock_flags = 0;
     chan->knock_flags = 0;
     chan->flood_lock_time = 120;
     chan->flood_lock_time = 120;
     chan->flood_exempt_mode = 0;
     chan->flood_exempt_mode = 0;

+ 3 - 1
src/mod/channels.mod/userchan.c

@@ -734,7 +734,7 @@ flood-exempt %d flood-lock-time %d knock %d \
 %cmeankicks %cenforcebans %cdynamicbans %cuserbans %cbitch \
 %cmeankicks %cenforcebans %cdynamicbans %cuserbans %cbitch \
 %cprivate %ccycle %cinactive %cdynamicexempts %cuserexempts \
 %cprivate %ccycle %cinactive %cdynamicexempts %cuserexempts \
 %cdynamicinvites %cuserinvites %cnodesynch %cclosed %cvoice \
 %cdynamicinvites %cuserinvites %cnodesynch %cclosed %cvoice \
-%cfastop %cautoop %cbotbitch %cbackup %cnomassjoin %crbl %cvoicebitch %c%s",
+%cfastop %cautoop %cbotbitch %cbackup %cnomassjoin %crbl %cvoicebitch %cprotect protect-backup %d %c%s",
 	w,
 	w,
 /* Chanchar template
 /* Chanchar template
  *      temp,
  *      temp,
@@ -790,6 +790,8 @@ flood-exempt %d flood-lock-time %d knock %d \
         PLSMNS(channel_nomassjoin(chan)),
         PLSMNS(channel_nomassjoin(chan)),
         PLSMNS(channel_rbl(chan)),
         PLSMNS(channel_rbl(chan)),
         PLSMNS(channel_voicebitch(chan)),
         PLSMNS(channel_voicebitch(chan)),
+        PLSMNS(channel_protect(chan)),
+        chan->protect_backup,
 	HAVE_TAKE ? PLSMNS(channel_take(chan)) : ' ',
 	HAVE_TAKE ? PLSMNS(channel_take(chan)) : ' ',
         HAVE_TAKE ? "take " : " "
         HAVE_TAKE ? "take " : " "
 /* Chanflag template
 /* Chanflag template

+ 6 - 1
src/mod/irc.mod/chan.c

@@ -704,6 +704,8 @@ static bool detect_chan_flood(char *floodnick, char *floodhost, char *from,
         dprintf(DP_MODE, "KICK %s %s :%s%s\n", chan->name, floodnick, kickprefix, response(RES_KICKFLOOD));
         dprintf(DP_MODE, "KICK %s %s :%s%s\n", chan->name, floodnick, kickprefix, response(RES_KICKFLOOD));
 	m->flags |= SENTKICK;
 	m->flags |= SENTKICK;
       }
       }
+      if (channel_protect(chan))
+        do_protect(chan, "Mass Kick");
     return 1;
     return 1;
     case FLOOD_DEOP:
     case FLOOD_DEOP:
       if (me_op(chan) && !chan_sentkick(m)) {
       if (me_op(chan) && !chan_sentkick(m)) {
@@ -718,6 +720,8 @@ static bool detect_chan_flood(char *floodnick, char *floodhost, char *from,
         simple_snprintf(s, sizeof(s), "Mass deop on %s by %s", chan->dname, from);
         simple_snprintf(s, sizeof(s), "Mass deop on %s by %s", chan->dname, from);
         deflag_user(u, DEFLAG_MDOP, s, chan);
         deflag_user(u, DEFLAG_MDOP, s, chan);
       }
       }
+      if (channel_protect(chan))
+        do_protect(chan, "Mass Deop");
       return 1;
       return 1;
     }
     }
   }
   }
@@ -2882,9 +2886,10 @@ static int gotkick(char *from, char *origmsg)
     struct userrec *u = NULL;
     struct userrec *u = NULL;
     struct flag_record fr = {FR_GLOBAL | FR_CHAN, 0, 0, 0 };
     struct flag_record fr = {FR_GLOBAL | FR_CHAN, 0, 0, 0 };
 
 
-    chan->channel.fighting++;
     fixcolon(msg);
     fixcolon(msg);
     u = get_user_by_host(from);
     u = get_user_by_host(from);
+    if (!u || (u && !u->bot))
+      chan->channel.fighting++;
     strlcpy(uhost, from, sizeof(buf));
     strlcpy(uhost, from, sizeof(buf));
     whodid = splitnick(&uhost);
     whodid = splitnick(&uhost);
     detect_chan_flood(whodid, uhost, from, chan, FLOOD_KICK, nick);
     detect_chan_flood(whodid, uhost, from, chan, FLOOD_KICK, nick);

+ 20 - 8
src/mod/irc.mod/irc.c

@@ -1439,17 +1439,29 @@ check_servers(struct chanset_t *chan)
   }
   }
 }
 }
 
 
+static void do_protect(struct chanset_t* chan, const char* reason) {
+  // Don't bother with these if already botbitch, already processed it, or it's a hacked bot and +botbitch won't help.
+  if (!channel_botbitch(chan)) {
+    if (chan->protect_backup) {
+      putlog(LOG_MISC, "*", "%s detected in %s: Setting +botbitch/+backup to protect the channel.", reason, chan->dname);
+      do_chanset(NULL, chan, "+botbitch +bitch +backup", DO_LOCAL | DO_NET);
+    } else {
+      putlog(LOG_MISC, "*", "%s detected in %s: Setting +botbitch to protect the channel.", reason, chan->dname);
+      do_chanset(NULL, chan, "+botbitch +bitch", DO_LOCAL | DO_NET);
+    }
+//        enforce_closed(chan);
+//        dprintf(DP_MODE, "TOPIC %s :Auto-closed - channel fight\n", chan->name);
+//    enforce_bitch(chan);
+    reversing = 1; // Reverse any modes which triggered this.
+  }
+}
+
 static void
 static void
 check_netfight(struct chanset_t *chan)
 check_netfight(struct chanset_t *chan)
 {
 {
-  if (fight_threshold) {
-    if ((chan->channel.fighting) && (chan->channel.fighting > fight_threshold)) {
-      if (!chan_bitch(chan) || !channel_closed(chan)) {
-        putlog(LOG_WARN, "*", "Auto-closed %s - channel fight", chan->dname);
-        do_chanset(NULL, chan, "+bitch +closed +backup", DO_LOCAL | DO_NET);
-        enforce_closed(chan);
-        dprintf(DP_MODE, "TOPIC %s :Auto-closed - channel fight\n", chan->name);
-      }
+  if (channel_protect(chan) && fight_threshold) {
+    if ((chan->channel.fighting) && (chan->channel.fighting > fight_threshold) && !chan_bitch(chan)) {
+      do_protect(chan, "Channel fight");
     }
     }
   }
   }
   chan->channel.fighting = 0;   /* we put this here because we need to clear it once per min */
   chan->channel.fighting = 0;   /* we put this here because we need to clear it once per min */

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

@@ -71,6 +71,7 @@ static bool any_ops(struct chanset_t *);
 static char *getchanmode(struct chanset_t *);
 static char *getchanmode(struct chanset_t *);
 static void flush_mode(struct chanset_t *, int);
 static void flush_mode(struct chanset_t *, int);
 static bool member_getuser(memberlist* m, bool act_on_lookup = 0);
 static bool member_getuser(memberlist* m, bool act_on_lookup = 0);
+static void do_protect(struct chanset_t* chan, const char* reason);
 
 
 /* reset(bans|exempts|invites) are now just macros that call resetmasks
 /* reset(bans|exempts|invites) are now just macros that call resetmasks
  * in order to reduce the code duplication. <cybah>
  * in order to reduce the code duplication. <cybah>

+ 33 - 18
src/mod/irc.mod/mode.c

@@ -1070,21 +1070,25 @@ gotmode(char *from, char *msg)
       if (me_op(chan)) {
       if (me_op(chan)) {
         char tmp[1024] = "";
         char tmp[1024] = "";
 
 
-        if (role && u && !u->bot) {
-          if (m && !chan_sentkick(m) && deops >= 3 && chan->mdop) {
-            if (role < 5) {
-              m->flags |= SENTKICK;
-              const size_t len = simple_snprintf(tmp, sizeof(tmp), "KICK %s %s :%s%s\r\n", chan->name, m->nick, kickprefix, response(RES_MASSDEOP));
-              if (role <= 2)
-                tputs(serv, tmp, len);
-              else
-                dprintf(DP_MODE, "%s", tmp);
-            } else {
-              if (u) {
-                simple_snprintf(tmp, sizeof(tmp), "Mass deop on %s by %s", chan->dname, m->nick);
-                deflag_user(u, DEFLAG_MDOP, tmp, chan);
+        if (!isserver[0] && role && (!u || (u && !u->bot))) {
+          if (m && deops >= 3) {
+            if (chan->mdop) {
+              if (role < 5 && !chan_sentkick(m)) {
+                m->flags |= SENTKICK;
+                const size_t len = simple_snprintf(tmp, sizeof(tmp), "KICK %s %s :%s%s\r\n", chan->name, m->nick, kickprefix, response(RES_MASSDEOP));
+                if (role <= 2)
+                  tputs(serv, tmp, len);
+                else
+                  dprintf(DP_MODE, "%s", tmp);
+              } else {
+                if (u) {
+                  simple_snprintf(tmp, sizeof(tmp), "Mass deop on %s by %s", chan->dname, m->nick);
+                  deflag_user(u, DEFLAG_MDOP, tmp, chan);
+                }
               }
               }
             }
             }
+            if (channel_protect(chan))
+              do_protect(chan, "Mass Deop");
           }
           }
 
 
           /* check for mop */
           /* check for mop */
@@ -1105,8 +1109,12 @@ gotmode(char *from, char *msg)
                   }
                   }
                 }
                 }
               }
               }
-              enforce_bitch(chan);        /* deop quick! */
+              // Don't mass deop if protect is set, it'll happen anyway below
+              if (!channel_protect(chan))
+                enforce_bitch(chan);        /* deop quick! */
             }
             }
+            if (channel_protect(chan))
+              do_protect(chan, "Mass OP");
           }
           }
         }
         }
         if (ops) {
         if (ops) {
@@ -1380,7 +1388,8 @@ gotmode(char *from, char *msg)
             }
             }
             break;
             break;
           case 'o':
           case 'o':
-            chan->channel.fighting++;
+            if (!u || (u && !u->bot))
+              chan->channel.fighting++;
             mv = assert_ismember(chan, mparam);
             mv = assert_ismember(chan, mparam);
             if (mv) {
             if (mv) {
               if (msign == '+')
               if (msign == '+')
@@ -1442,21 +1451,24 @@ gotmode(char *from, char *msg)
             }
             }
             break;
             break;
           case 'b':
           case 'b':
-            chan->channel.fighting++;
+            if (!u || (u && !u->bot))
+              chan->channel.fighting++;
             if (msign == '+')
             if (msign == '+')
               got_ban(chan, m, mparam, isserver);
               got_ban(chan, m, mparam, isserver);
             else
             else
               got_unban(chan, m, mparam);
               got_unban(chan, m, mparam);
             break;
             break;
           case 'e':
           case 'e':
-            chan->channel.fighting++;
+            if (!u || (u && !u->bot))
+              chan->channel.fighting++;
             if (msign == '+')
             if (msign == '+')
               got_exempt(chan, m, mparam, isserver);
               got_exempt(chan, m, mparam, isserver);
             else
             else
               got_unexempt(chan, m, mparam);
               got_unexempt(chan, m, mparam);
             break;
             break;
           case 'I':
           case 'I':
-            chan->channel.fighting++;
+            if (!u || (u && !u->bot))
+              chan->channel.fighting++;
             if (msign == '+')
             if (msign == '+')
               got_invite(chan, m, mparam, isserver);
               got_invite(chan, m, mparam, isserver);
             else
             else
@@ -1480,6 +1492,9 @@ gotmode(char *from, char *msg)
           }
           }
         }
         }
       }
       }
+      // If reversing, dump the modes out
+      if (reversing)
+        flush_mode(chan, QUICK);
       for (i = 0; i < modecnt; i++)
       for (i = 0; i < modecnt; i++)
         if (modes[i])
         if (modes[i])
           free(modes[i]);
           free(modes[i]);