MODE_NOFLOOD (+f [!][+@]<count>:<time>) added (prevents users from flooding the channel)
authorpk910 <philipp@zoelle1.de>
Fri, 1 Jul 2011 19:38:09 +0000 (21:38 +0200)
committerpk910 <philipp@zoelle1.de>
Fri, 1 Jul 2011 22:57:35 +0000 (00:57 +0200)
include/channel.h
include/supported.h
ircd/channel.c
ircd/ircd_relay.c

index 6984250e62ea1dd9143d7df2abeaf3843b151dae..dcd93ea394973394aeca24d1a99aac9a4c4c9aaf 100644 (file)
@@ -130,14 +130,15 @@ typedef signed long long long64;
 #define MODE_ADD         0x40000000
 #define MODE_ACCESS     0x100000000LLU /**< ChanServ access */
 #define MODE_AUDITORIUM 0x200000000LLU /**< +u Auditorium */
+#define MODE_NOFLOOD    0x400000000LLU /**< +f NoFlood */
 /** mode flags which take another parameter (With PARAmeterS)
  */
-#define MODE_WPARAS     (MODE_CHANOP|MODE_VOICE|MODE_BAN|MODE_KEY|MODE_LIMIT|MODE_APASS|MODE_UPASS|MODE_EXCEPTION|MODE_ALTCHAN|MODE_ACCESS)
+#define MODE_WPARAS     (MODE_CHANOP|MODE_VOICE|MODE_BAN|MODE_KEY|MODE_LIMIT|MODE_APASS|MODE_UPASS|MODE_EXCEPTION|MODE_ALTCHAN|MODE_ACCESS|MODE_NOFLOOD)
 
 /** Available Channel modes */
 #define infochanmodes feature_bool(FEAT_OPLEVELS) ? "AcCbiklmMnNopstuUvrDRzQu" : "cCbiklmMnNopstuvrDRzQu"
 /** Available Channel modes that take parameters */
-#define infochanmodeswithparams feature_bool(FEAT_OPLEVELS) ? "AbkloUvFa" : "bklovFa"
+#define infochanmodeswithparams feature_bool(FEAT_OPLEVELS) ? "AbfkloUvFa" : "bfklovFa"
 
 #define HoldChannel(x)          (!(x))
 /** name invisible */
@@ -205,10 +206,20 @@ struct Membership {
   struct Membership* prev_member;      /**< The previous user on this channel*/
   struct Membership* next_channel;     /**< Next channel this user is on */
   struct Membership* prev_channel;     /**< Previous channel this user is on*/
+  struct MemberFlood* flood;        /**< count's how many times a user sent something (+f floodcontrol) */
   unsigned int       status;           /**< Flags for op'd, voice'd, etc */
   unsigned short     oplevel;          /**< Op level */
 };
 
+#define FLFL_CHANOP  0x01
+#define FLFL_VOICE   0x02
+#define FLFL_NOFLOOD  0x04
+
+struct MemberFlood {
+  struct MemberFlood* next_memberflood;
+  time_t time;
+};
+
 #define MAXOPLEVELDIGITS    3
 #define MAXOPLEVEL          999
 
@@ -251,6 +262,8 @@ struct Mode {
   ulong64 mode;
   unsigned int limit;
   unsigned int access;
+  char noflood[11]; //max [@+]999:9999
+  unsigned int noflood_value; //3bit for @+, 10bit  first field,  14bit second field  = 27bit of 32bit
   char key[KEYLEN + 1];
   char upass[KEYLEN + 1];
   char apass[KEYLEN + 1];
@@ -469,5 +482,6 @@ extern int apply_ban(struct Ban **banlist, struct Ban *newban, int free);
 extern void free_ban(struct Ban *ban);
 extern signed int destruct_nonpers_channel(struct Channel *chptr);
 extern int ext_amsg_block(struct Client *cptr, struct Channel *chptr, const char *msg);
+extern int ext_noflood_block(struct Client *cptr, struct Channel *chptr);
 
 #endif /* INCLUDED_channel_h */
index 260ac6a0da48b847462bf2afdd8975564a48e9fe..31b044d850ee67731f0ae74dc014386c2faf077b 100644 (file)
@@ -65,7 +65,7 @@
 #define FEATURESVALUES2 NICKLEN, TOPICLEN, AWAYLEN, TOPICLEN, \
                         feature_int(FEAT_CHANNELLEN), CHANNELLEN, \
                         (feature_bool(FEAT_LOCAL_CHANNELS) ? "#&" : "#"), "(ov)@+", "@+", \
-                        (feature_bool(FEAT_OPLEVELS) ? "b,AkU,al,cCimMnNprstuDdRz" : "b,k,al,cCimMnNprstuDdRz"), \
+                        (feature_bool(FEAT_OPLEVELS) ? "b,AkU,alfF,cCimMnNprstuDdRz" : "b,k,alfF,cCimMnNprstuDdRz"), \
                         "rfc1459", feature_str(FEAT_NETWORK)
 
 #endif /* INCLUDED_supported_h */
index decfd78975f7d8137083d5b1216403de61821dac..d7630b1f598a1f61c17725c9ef95bb751b103643 100644 (file)
@@ -68,6 +68,8 @@ static unsigned int membershipAllocCount;
 static struct Membership* membershipFreeList;
 /** Freelist for struct Ban*'s */
 static struct Ban* free_bans;
+/** Freelist for struct MemberFlood*'s */
+static struct MemberFlood* free_MemberFlood;
 /** Number of ban structures allocated. */
 static size_t bans_alloc;
 /** Number of ban structures in use. */
@@ -489,6 +491,7 @@ void add_user_to_channel(struct Channel* chptr, struct Client* who,
     member->user         = who;
     member->channel      = chptr;
     member->status       = flags;
+    member->flood        = NULL;
     SetOpLevel(member, oplevel);
 
     member->next_member  = chptr->members;
@@ -549,6 +552,8 @@ static int remove_member_from_channel(struct Membership* member)
 
   --(cli_user(member->user))->joined;
 
+  member->flood = NULL;
+  
   member->next_member = membershipFreeList;
   membershipFreeList = member;
 
@@ -891,6 +896,13 @@ void channel_modes(struct Client *cptr, char *mbuf, char *pbuf, int buflen,
     strcat(pbuf, chptr->mode.altchan);
     previous_parameter = 1;
   }
+  if (*chptr->mode.noflood) {
+    *mbuf++ = 'f';
+    if (previous_parameter)
+      strcat(pbuf, " ");
+    strcat(pbuf, chptr->mode.noflood);
+    previous_parameter = 1;
+  }
   if (*chptr->mode.key) {
     *mbuf++ = 'k';
     if (previous_parameter)
@@ -1601,6 +1613,7 @@ modebuf_flush_int(struct ModeBuf *mbuf, int all)
     MODE_NOCTCP,    'C',
 /*  MODE_EXCEPTION, 'e', */
 /*  MODE_ALTCHAN,   'F', */
+/*  MODE_NOFLOOD,   'f', */
     MODE_ACCESS,    'a',
     MODE_NOAMSGS,   'M',
        MODE_NONOTICE,  'N',
@@ -1697,14 +1710,14 @@ modebuf_flush_int(struct ModeBuf *mbuf, int all)
        bufptr[(*bufptr_i)++] = MB_TYPE(mbuf, i) & MODE_CHANOP ? 'o' : 'v';
        totalbuflen -= IRCD_MAX(9, tmp) + 1;
       }
-    } else if (MB_TYPE(mbuf, i) & (MODE_BAN | MODE_EXCEPTION | MODE_APASS | MODE_UPASS | MODE_ALTCHAN)) {
+    } else if (MB_TYPE(mbuf, i) & (MODE_BAN | MODE_EXCEPTION | MODE_APASS | MODE_UPASS | MODE_ALTCHAN | MODE_NOFLOOD)) {
       tmp = strlen(MB_STRING(mbuf, i));
 
       if ((totalbuflen - tmp) <= 0) /* don't overflow buffer */
        MB_TYPE(mbuf, i) |= MODE_SAVE; /* save for later */
       else {
        char mode_char;
-       switch(MB_TYPE(mbuf, i) & (MODE_BAN | MODE_EXCEPTION | MODE_APASS | MODE_UPASS | MODE_ALTCHAN))
+       switch(MB_TYPE(mbuf, i) & (MODE_BAN | MODE_EXCEPTION | MODE_APASS | MODE_UPASS | MODE_ALTCHAN | MODE_NOFLOOD))
        {
          case MODE_APASS:
            mode_char = 'A';
@@ -1718,6 +1731,9 @@ modebuf_flush_int(struct ModeBuf *mbuf, int all)
       case MODE_ALTCHAN:
                mode_char = 'F';
                break;
+         case MODE_NOFLOOD:
+               mode_char = 'f';
+               break;
          default:
            mode_char = 'b';
            break;
@@ -1820,6 +1836,9 @@ modebuf_flush_int(struct ModeBuf *mbuf, int all)
        
          else if (MB_TYPE(mbuf, i) & MODE_ALTCHAN)
        build_string(strptr, strptr_i, MB_STRING(mbuf, i), 0, ' ');
+         else if ((MB_TYPE(mbuf, i) & (MODE_ADD | MODE_NOFLOOD)) ==
+              (MODE_ADD | MODE_NOFLOOD))
+       build_string(strptr, strptr_i, MB_STRING(mbuf, i), 0, ' ');
     }
 
     /* send the messages off to their destination */
@@ -1908,7 +1927,7 @@ modebuf_flush_int(struct ModeBuf *mbuf, int all)
        build_string(strptr, strptr_i, NumNick(MB_CLIENT(mbuf, i)), ' ');
 
       /* deal with modes that take strings */
-      else if (MB_TYPE(mbuf, i) & (MODE_KEY | MODE_BAN | MODE_EXCEPTION | MODE_APASS | MODE_UPASS | MODE_ALTCHAN))
+      else if (MB_TYPE(mbuf, i) & (MODE_KEY | MODE_BAN | MODE_EXCEPTION | MODE_APASS | MODE_UPASS | MODE_ALTCHAN | MODE_NOFLOOD))
        build_string(strptr, strptr_i, MB_STRING(mbuf, i), 0, ' ');
 
       /*
@@ -2223,13 +2242,14 @@ modebuf_extract(struct ModeBuf *mbuf, char *buf)
        MODE_ALTCHAN,    'F',
        MODE_ACCESS,    'a',
        MODE_AUDITORIUM, 'u',
+       MODE_NOFLOOD,   'f',
     0x0, 0x0
   };
   ulong64 add;
   int i, bufpos = 0, len;
   ulong64 *flag_p;
   char *key = 0, limitbuf[20], accessbuf[20];
-  char *apass = 0, *upass = 0, *altchan = 0;
+  char *apass = 0, *upass = 0, *altchan = 0, *noflood = 0;
 
   assert(0 != mbuf);
   assert(0 != buf);
@@ -2240,7 +2260,7 @@ modebuf_extract(struct ModeBuf *mbuf, char *buf)
 
   for (i = 0; i < mbuf->mb_count; i++) { /* find keys and limits */
     if (MB_TYPE(mbuf, i) & MODE_ADD) {
-      add |= MB_TYPE(mbuf, i) & (MODE_KEY | MODE_LIMIT | MODE_APASS | MODE_UPASS | MODE_ALTCHAN | MODE_ACCESS);
+      add |= MB_TYPE(mbuf, i) & (MODE_KEY | MODE_LIMIT | MODE_APASS | MODE_UPASS | MODE_ALTCHAN | MODE_ACCESS | MODE_NOFLOOD);
 
       if (MB_TYPE(mbuf, i) & MODE_KEY) /* keep strings */
        key = MB_STRING(mbuf, i);
@@ -2254,6 +2274,8 @@ modebuf_extract(struct ModeBuf *mbuf, char *buf)
        apass = MB_STRING(mbuf, i);
       else if (MB_TYPE(mbuf, i) & MODE_ALTCHAN)
        altchan = MB_STRING(mbuf, i);
+         else if (MB_TYPE(mbuf, i) & MODE_NOFLOOD)
+       noflood = MB_STRING(mbuf, i);
     }
   }
 
@@ -2279,6 +2301,8 @@ modebuf_extract(struct ModeBuf *mbuf, char *buf)
       build_string(buf, &bufpos, apass, 0, ' ');
        else if (buf[i] == 'F')
       build_string(buf, &bufpos, altchan, 0, ' ');
+       else if (buf[i] == 'f')
+      build_string(buf, &bufpos, noflood, 0, ' ');
   }
 
   buf[bufpos] = '\0';
@@ -2330,6 +2354,7 @@ mode_invite_clear(struct Channel *chan)
 #define DONE_EXCEPTIONLIST 0x400 /**< We've sent the exception list */
 #define DONE_ALTCHAN   0x800   /**< We've set the altchan */
 #define DONE_ACCESS    0x1000  /**< We've set the access */
+#define DONE_NOFLOOD   0x2000  /**< We've set the noflood options */
 
 struct ParseState {
   struct ModeBuf *mbuf;
@@ -2601,6 +2626,110 @@ mode_parse_altchan(struct ParseState *state, ulong64 *flag_p)
   }
 }
 
+static void
+mode_parse_noflood(struct ParseState *state, ulong64 *flag_p)
+{
+  char *t_str;
+  char *tmp;
+  unsigned int count = 0, time = 0, flags = 0;
+
+  if (state->dir == MODE_ADD) { /* convert arg only if adding noflood */
+    if (MyUser(state->sptr) && state->max_args <= 0) /* too many args? */
+      return;
+    
+    if (state->parc <= 0) { /* warn if not enough args */
+      if (MyUser(state->sptr))
+       need_more_params(state->sptr, "MODE +f");
+      return;
+    }
+
+    t_str = state->parv[state->args_used++]; /* grab arg */
+    state->parc--;
+    state->max_args--;
+
+    tmp = t_str;
+    
+    if(tmp[0] == '!') {
+        if(state->flags & MODE_PARSE_FORCE) flags |= FLFL_NOFLOOD;
+        else t_str++; //simply ignore if it's not an opmode
+        tmp++;
+    }
+    if(tmp[0] == '+' || tmp[0] == '@') {
+        if(tmp[0] == '+') flags |= FLFL_VOICE;
+        if(tmp[0] == '@') flags |= FLFL_CHANOP;
+        tmp++;
+    }
+    char *p;
+    for(p = tmp; p[0]; p++) {
+      if(p[0] == ':') {
+        char tmpchar = p[0];
+        p[0] = '\0';
+        count = strtoul(tmp,0,10);
+        p[0] = tmpchar;
+        p++;
+        time = strtoul(p,0,10);
+        break;
+      }
+    }
+    if(count <= 0 || time <= 0 || count > 999 || time > 9999) return;
+    
+    if (!(state->flags & MODE_PARSE_WIPEOUT) &&
+       (!t_str || t_str == state->chptr->mode.noflood))
+      return;
+  } else
+    t_str = state->chptr->mode.noflood;
+
+  /* If they're not an oper, they can't change modes */
+  if (state->flags & (MODE_PARSE_NOTOPER | MODE_PARSE_NOTMEMBER)) {
+    send_notoper(state);
+    return;
+  }
+
+  /* Can't remove a noflood that's not there */
+  if (state->dir == MODE_DEL && !*state->chptr->mode.noflood)
+    return;
+    
+  /* Skip if this is a burst and a lower noflood than this is set already */
+  if ((state->flags & MODE_PARSE_BURST) &&
+      *(state->chptr->mode.noflood))
+    return;
+
+  if (state->done & DONE_NOFLOOD) /* allow noflood to be set only once */
+    return;
+  state->done |= DONE_NOFLOOD;
+
+  if (!state->mbuf)
+    return;
+
+  if (!(state->flags & MODE_PARSE_WIPEOUT) && state->dir == MODE_ADD &&
+      !ircd_strcmp(state->chptr->mode.noflood, t_str))
+    return; /* no change */
+
+  if (state->flags & MODE_PARSE_BOUNCE) {
+    if (*state->chptr->mode.noflood) /* reset old noflood */
+      modebuf_mode_string(state->mbuf, MODE_DEL | flag_p[0], state->chptr->mode.noflood, 0);
+    else /* remove new bogus noflood */
+      modebuf_mode_string(state->mbuf, MODE_ADD | flag_p[0], t_str, 0);
+  } else /* send new noflood */
+    modebuf_mode_string(state->mbuf, state->dir | flag_p[0], t_str, 0);
+
+  if (state->flags & MODE_PARSE_SET) {
+    if (state->dir == MODE_DEL) /* remove the old noflood */
+      *state->chptr->mode.noflood = '\0';
+    else
+      ircd_strncpy(state->chptr->mode.noflood, t_str, CHANNELLEN);
+  }
+  
+  if (state->dir == MODE_ADD) {
+    unsigned int noflood_value = time;
+    noflood_value <<= 10;
+    noflood_value |= count;
+    noflood_value <<= 3;
+    noflood_value |= flags;
+    state->chptr->mode.noflood_value = noflood_value;
+  }
+}
+
 static void
 mode_parse_quarantine(struct ParseState *state, ulong64 *flag_p)
 {
@@ -3639,6 +3768,7 @@ mode_parse(struct ModeBuf *mbuf, struct Client *cptr, struct Client *sptr,
        MODE_ALTCHAN,        'F',
     MODE_ACCESS,        'a',
        MODE_AUDITORIUM,    'u',
+       MODE_NOFLOOD,       'f',
     MODE_ADD,          '+',
     MODE_DEL,          '-',
     0x0, 0x0
@@ -3709,6 +3839,9 @@ mode_parse(struct ModeBuf *mbuf, struct Client *cptr, struct Client *sptr,
        break;
          case 'F':
        mode_parse_altchan(&state, flag_p);
+       break;
+         case 'f':
+       mode_parse_noflood(&state, flag_p);
        break;
       case 'Q':
       if(IsNetServ(state.sptr) && IsSecurityServ(state.sptr))
@@ -3856,6 +3989,9 @@ mode_parse(struct ModeBuf *mbuf, struct Client *cptr, struct Client *sptr,
     if (state.chptr->mode.altchan && !(state.done & DONE_ALTCHAN))
       modebuf_mode_string(state.mbuf, MODE_DEL | MODE_ALTCHAN,
                        state.chptr->mode.altchan, 0);
+       if (state.chptr->mode.noflood && !(state.done & DONE_NOFLOOD))
+      modebuf_mode_string(state.mbuf, MODE_DEL | MODE_NOFLOOD,
+                       state.chptr->mode.noflood, 0);
     if (*state.chptr->mode.key && !(state.done & DONE_KEY_DEL))
       modebuf_mode_string(state.mbuf, MODE_DEL | MODE_KEY,
                          state.chptr->mode.key, 0);
@@ -4194,3 +4330,59 @@ int ext_amsg_block(struct Client *cptr, struct Channel *chptr, const char *msg)
   return 0;
 }
 
+/** Extended flood check.
+ * The channelmode MODE_NOFLOOD prevents users from flooding the channel.
+ * 
+ * This function returns 0 if the message may pass and 1 if the message should
+ * be blocked.
+ * For this function to work properly it must be called on every PRIVMSG which
+ * is sent by any user.
+ *
+ * --pk910 2011/7/1
+ */
+int ext_noflood_block(struct Client *cptr, struct Channel *chptr) {
+  if(!chptr->mode.noflood) return 0;
+  struct Membership *member = find_member_link(chptr, cptr);
+  if(!member) return 0; //TODO: we've no check for -n channels implemented, yet
+  //check if this user is really affected by +f
+  unsigned int flags = (chptr->mode.noflood_value & 0x00000007);        //0000 0000 0000 0000 0000 0000 0000 0111 = 0x00000007 >> 0
+  unsigned int count = (chptr->mode.noflood_value & 0x00001ff8) >> 3;   //0000 0000 0000 0000 0001 1111 1111 1000 = 0x00001ff8 >> 3
+  int time           = (chptr->mode.noflood_value & 0x07ffe000) >> 13;  //0000 0111 1111 1111 1110 0000 0000 0000 = 0x07ffe000 >> 13
+  if(!(flags & FLFL_NOFLOOD) && HasPriv(cptr, PRIV_FLOOD))
+    return 0;
+  if(!(flags & FLFL_CHANOP) && (member->status & CHFL_CHANOP)) 
+    return 0;
+  if(!(flags & (FLFL_CHANOP | FLFL_VOICE)) && (member->status & CHFL_VOICE)) 
+    return 0;
+  int floodcount = 0;
+  struct MemberFlood *floodnode, *prev_floodnode;
+  for (floodnode = member->flood; floodnode; floodnode = floodnode->next_memberflood) {
+    if(floodnode->time + time > CurrentTime) {
+      if(floodcount == 0 && floodnode != member->flood) {
+        //free all before
+        prev_floodnode->next_memberflood = free_MemberFlood;
+        free_MemberFlood  = prev_floodnode;
+        member->flood = floodnode;
+      }
+      floodcount++;
+    }
+    prev_floodnode = floodnode;
+  }
+  Debug((DEBUG_INFO, "floodcount: %i", floodcount));
+  if(floodcount >= count) return 1; //blocked!
+  //add a new floodnode :)
+  if(free_MemberFlood) {
+    floodnode = free_MemberFlood;
+    free_MemberFlood = floodnode->next_memberflood;
+  } else
+    floodnode = (struct MemberFlood*) MyMalloc(sizeof(struct MemberFlood));
+  memset(floodnode, 0, sizeof(struct MemberFlood));
+  floodnode->time = CurrentTime;
+  floodnode->next_memberflood = NULL;
+  if(floodcount > 0)
+    prev_floodnode->next_memberflood = floodnode;
+  else
+    member->flood = floodnode;
+  return 0;
+}
+
index 701c639f7d9960ea824feebf9a4277f1d42805f2..d62761edd2e9ecfa286aebf787fe879c5d31d7e7 100644 (file)
@@ -104,6 +104,11 @@ void relay_channel_message(struct Client* sptr, const char* name, const char* te
     send_reply(sptr, ERR_CANNOTSENDTOCHAN, chptr->chname);
     return;
   }
+  
+  if(!IsXtraOp(sptr) && ext_noflood_block(sptr, chptr)) {
+    send_reply(sptr, ERR_CANNOTSENDTOCHAN, chptr->chname);
+    return;
+  }
 
   /*
    * This first: Almost never a server/service
@@ -169,7 +174,12 @@ void relay_channel_notice(struct Client* sptr, const char* name, const char* tex
    */
   if(!IsXtraOp(sptr) && !HasPriv(sptr, PRIV_NOAMSG_OVERRIDE) && ext_amsg_block(sptr, chptr, text))
     return;
-       
+
+  if(!IsXtraOp(sptr) && ext_noflood_block(sptr, chptr)) {
+    send_reply(sptr, ERR_CANNOTSENDTOCHAN, chptr->chname);
+    return;
+  }
+
   /* Now we check if the channel has the NONOTICE mode
    * If NONOTICE is set only XtraOPS or ColorOverride users can send notices!
    */