Jelajahi Sumber

Merge branch 'dynamic-roles' into next

* dynamic-roles: (23 commits)
  Rebalance when a roled bot is modified
  Rebalance roles more often
  Don't reset roles until rebalancing
  Track by handle, not nick
  Move pending logic to rebalance_roles() due to new event-based trigger
  Only consider bots that can be opped to have roles
  Fix memory leak when removing a channel with roles
  Rebalance roles on only after channel resets
  Add cmd_roles
  Remove old role code no longer needed
  Add new consistent dynamic roles
  Display all role names in cmd_debug
  Fix crash on doflood(NULL)
  Move global role to chanset_t.role
  Add fields to track roles per chan
  Define count of roles needed
  Move role to flags.c
  Convert manop to new role checks
  Add ROLE_PROTECT
  Add a role for enforcing chanmode
  ...

Conflicts:
	doc/UPDATES
Bryan Drewery 12 tahun lalu
induk
melakukan
a066b8e5cd

+ 1 - 0
doc/UPDATES

@@ -3,6 +3,7 @@ next
   * Properly honor exemptions when kicking matched RBL clients
   * Properly honor exemptions when kicking matched RBL clients
   * Fix LASTON not being shared
   * Fix LASTON not being shared
   * Remove channel limit when limitraise is disabled (#77)
   * Remove channel limit when limitraise is disabled (#77)
+  * Add cmd_roles (leaf only) to display roles for a channel.
 
 
 maint
 maint
   * Read in /etc/hosts on startup
   * Read in /etc/hosts on startup

+ 26 - 4
doc/help.txt

@@ -1089,7 +1089,7 @@ See also: console, channels%{+m}, status%{-}
 :hub:groups
 :hub:groups
 ###  $bgroups$b [bot]
 ###  $bgroups$b [bot]
    Shows the list of groups and which bots are in them.
    Shows the list of groups and which bots are in them.
-
+ 
    Specify a bot to only show which groups it is in.
    Specify a bot to only show which groups it is in.
  
  
 See also: bots, downbots%{+n}, bottree%{-}
 See also: bots, downbots%{+n}, bottree%{-}
@@ -1608,6 +1608,28 @@ See also: resetbans, resetinvites
    Makes the bot restart, but keeps its connection to IRC active.
    Makes the bot restart, but keeps its connection to IRC active.
  
  
 See also: rehash, reload, save
 See also: rehash, reload, save
+:leaf:roles
+###  $broles$b [channel]
+   Displays which bots are fullfilling which roles for a channel.
+ 
+   Some roles replace older botflags. See '%dhelp whois' for more
+   information.
+ 
+   $bRole$b         $bFlag$b     $bDescription$b
+   voice        y        Voice any +v users and handle +voice.
+   flood        f        Handle channel flood controls.
+   op           y        Auto-op +O users and handle autoop.
+   deop                  Deop clients on +bitch or +revenge needs.
+   kick                  Kick clients on +bitch, +revenge or +k needs.
+   ban                   Kick clients on +bitch, +revenge or +k needs.
+   topic                 Set the topic as needed for topic protect.
+   limit        l        Handle automatically raising limit as needed.
+   resolv       r        DNS all clients to look for ban/op matching.
+   revenge               React to +revenge needs.
+   chanmode              Set channel protect modes as needed.
+   protect               Handle +protect needs.
+ 
+See also: whois%{+m|m}, chanset, chaninfo%{-}
 :hub:save
 :hub:save
 ###  $bsave$b
 ###  $bsave$b
    This makes the bot write its entire userfile to disk. This is useful if you
    This makes the bot write its entire userfile to disk. This is useful if you
@@ -1718,10 +1740,10 @@ See also: reload, backup
                          known users. Note that this will break msg-ident.
                          known users. Note that this will break msg-ident.
 [B]  $bfish-auto-keyx$b      Whether to automatically do a DH1010 FiSH Key exchange when
 [B]  $bfish-auto-keyx$b      Whether to automatically do a DH1010 FiSH Key exchange when
                          accepting users. This is only supported if using callerid.
                          accepting users. This is only supported if using callerid.
-
+ 
 [B]  $bfish-paranoid$b       Whether to automatically re-keyexchange after every message. This
 [B]  $bfish-paranoid$b       Whether to automatically re-keyexchange after every message. This
                          mitigates replay attacks.
                          mitigates replay attacks.
-
+ 
 [S]  $baltchars$b            Define string of characters to cycle when generating alternative
 [S]  $baltchars$b            Define string of characters to cycle when generating alternative
                          nicks when nick is taken. Ie: _-`[].
                          nicks when nick is taken. Ie: _-`[].
 [S]  $bjupenick$b            The bot will attempt to jupe this nick. It will never use a variation
 [S]  $bjupenick$b            The bot will attempt to jupe this nick. It will never use a variation
@@ -1909,7 +1931,7 @@ See also: find
 ###  $btopic$b [channel] [new topic]
 ###  $btopic$b [channel] [new topic]
    Changes the channel's topic, assuming the bot is a chanop or the
    Changes the channel's topic, assuming the bot is a chanop or the
    channel is not +t (uses your current console channel).
    channel is not +t (uses your current console channel).
-
+ 
    If no chanel is specified, the console channel is used.
    If no chanel is specified, the console channel is used.
    If no new topic is specified, the current topic of the channel will be displayed.
    If no new topic is specified, the current topic of the channel will be displayed.
  
  

+ 0 - 38
src/botnet.c

@@ -1789,45 +1789,7 @@ void zapfbot(int idx)
   lostdcc(idx);
   lostdcc(idx);
 }
 }
 
 
-static int get_role(char *bot)
-{
-  struct userrec *u2 = NULL;
-
-  if (!(u2 = get_user_by_handle(userlist, bot)))
-    return 1;
-  if (bot_hublevel(u2) != 999)
-    return 0;
-
-  int rl, i;
-  struct bot_addr *ba = NULL;
-  int r[5] = { 0, 0, 0, 0, 0 };
-  struct userrec *u = NULL;
-
-  for (u = userlist; u; u = u->next) {
-    if (u->bot && bot_hublevel(u) == 999) {
-      if (strcmp(u->handle, bot)) {
-        ba = (struct bot_addr *) get_user(&USERENTRY_BOTADDR, u);
-        if ((nextbot(u->handle) >= 0) && (ba) && (ba->roleid > 0) && (ba->roleid < 5))
-          r[(ba->roleid - 1)]++;
-      }
-    }
-  }
-  rl = 0;
-  for (i = 1; i <= 4; i++)
-    if (r[i] < r[rl])
-      rl = i;
-  rl++;
-  ba = (struct bot_addr *) get_user(&USERENTRY_BOTADDR, u2);
-  if (ba)
-    ba->roleid = rl;
-  return rl;
-}
-
 void lower_bot_linked(int idx)
 void lower_bot_linked(int idx)
 {
 {
-  char tmp[6] = "";
-
-  simple_snprintf(tmp, sizeof(tmp), "rl %d", get_role(dcc[idx].nick));
-  putbot(dcc[idx].nick, tmp);
 }
 }
 
 

+ 11 - 0
src/chan.h

@@ -257,6 +257,17 @@ struct chanset_t {
   char added_by[HANDLEN + 1];	/* who added the channel? */
   char added_by[HANDLEN + 1];	/* who added the channel? */
   char dname[81];               /* what the users know the channel as like !eggdev */
   char dname[81];               /* what the users know the channel as like !eggdev */
   char name[81];                /* what the servers know the channel as, like !ABCDEeggdev */
   char name[81];                /* what the servers know the channel as, like !ABCDEeggdev */
+
+  // bitmask of roles for each bot
+  bd::HashTable<bd::String, int> *bot_roles;
+
+  // List of bots for each role
+  bd::HashTable<short, bd::Array<bd::String> > *role_bots;
+
+  // My role bitmask
+  int role;
+
+  int needs_role_rebalance;
 };
 };
 
 
 /* behavior modes for the channel */
 /* behavior modes for the channel */

+ 17 - 2
src/cmds.c

@@ -2108,6 +2108,9 @@ static void cmd_suicide(int idx, char *par)
 static void cmd_debug(int idx, char *par)
 static void cmd_debug(int idx, char *par)
 {
 {
   char *cmd = NULL;
   char *cmd = NULL;
+  struct chanset_t* chan = NULL;
+  size_t roleidx;
+  bd::Array<bd::String> roles;
 
 
   if (!par[0]) 
   if (!par[0]) 
     putlog(LOG_CMDS, "*", "#%s# debug", dcc[idx].nick);
     putlog(LOG_CMDS, "*", "#%s# debug", dcc[idx].nick);
@@ -2118,8 +2121,20 @@ static void cmd_debug(int idx, char *par)
     dprintf(idx, "Timesync: %li (%li)\n", (long) (now + timesync), (long)timesync);
     dprintf(idx, "Timesync: %li (%li)\n", (long) (now + timesync), (long)timesync);
   if (!cmd || (cmd && !strcmp(cmd, "now")))
   if (!cmd || (cmd && !strcmp(cmd, "now")))
     dprintf(idx, "Now: %li\n", (long)now);
     dprintf(idx, "Now: %li\n", (long)now);
-  if (!cmd || (cmd && !strcmp(cmd, "role")))
-    dprintf(idx, "Role: %d\n", role);
+  if (!cmd || (cmd && !strcmp(cmd, "role"))) {
+    for (chan = chanset; chan; chan = chan->next) {
+      if (chan->role) {
+        roles.clear();
+        for (roleidx = 0; role_counts[roleidx].name; ++roleidx) {
+          if (chan->role & role_counts[roleidx].role) {
+            roles << role_counts[roleidx].name;
+          }
+        }
+        dprintf(idx, "Role: %-8s: %s\n", chan->dname,
+            static_cast<bd::String>(roles.join(" ")).c_str());
+      }
+    }
+  }
   if (!cmd || (cmd && !strcmp(cmd, "net")))
   if (!cmd || (cmd && !strcmp(cmd, "net")))
     tell_netdebug(idx);
     tell_netdebug(idx);
   if (!cmd || (cmd && !strcmp(cmd, "dns")))
   if (!cmd || (cmd && !strcmp(cmd, "dns")))

+ 31 - 0
src/flags.c

@@ -38,6 +38,22 @@
 
 
 flag_t FLAG[128];
 flag_t FLAG[128];
 
 
+struct rolecount role_counts[] = {
+  {"voice",	ROLE_VOICE,	1},
+  {"flood",	ROLE_FLOOD,	2},
+  {"op",	ROLE_OP,	1},
+  {"deop",	ROLE_DEOP,	1},
+  {"kick",	ROLE_KICK,	2},
+  {"ban",	ROLE_BAN,	2},
+  {"topic",	ROLE_TOPIC,	1},
+  {"limit",	ROLE_LIMIT,	1},
+  {"resolv",	ROLE_RESOLV,	2},
+  {"revenge",	ROLE_REVENGE,	3},
+  {"chanmode",	ROLE_CHANMODE,	1},
+  {"protect",	ROLE_PROTECT,	2},
+  {NULL,	0,		0},
+};
+
 void
 void
 init_flags()
 init_flags()
 {
 {
@@ -488,6 +504,9 @@ doresolv(const struct chanset_t *chan)
   if (!chan)
   if (!chan)
     return 0;
     return 0;
 
 
+  if (chan->role & ROLE_RESOLV)
+    return 1;
+
   struct flag_record fr = { FR_GLOBAL | FR_CHAN | FR_BOT, 0, 0, 0 };
   struct flag_record fr = { FR_GLOBAL | FR_CHAN | FR_BOT, 0, 0, 0 };
 
 
   get_user_flagrec(conf.bot->u, &fr, chan->dname);
   get_user_flagrec(conf.bot->u, &fr, chan->dname);
@@ -502,6 +521,9 @@ dovoice(const struct chanset_t *chan)
   if (!chan)
   if (!chan)
     return 0;
     return 0;
 
 
+  if (chan->role & ROLE_VOICE)
+    return 1;
+
   struct flag_record fr = { FR_GLOBAL | FR_CHAN | FR_BOT, 0, 0, 0 };
   struct flag_record fr = { FR_GLOBAL | FR_CHAN | FR_BOT, 0, 0, 0 };
 
 
   get_user_flagrec(conf.bot->u, &fr, chan->dname);
   get_user_flagrec(conf.bot->u, &fr, chan->dname);
@@ -513,6 +535,12 @@ dovoice(const struct chanset_t *chan)
 int
 int
 doflood(const struct chanset_t *chan)
 doflood(const struct chanset_t *chan)
 {
 {
+  if (!chan)
+    return 0;
+
+  if (chan->role & ROLE_FLOOD)
+    return 1;
+
   struct flag_record fr = { FR_GLOBAL | FR_CHAN | FR_BOT, 0, 0, 0 };
   struct flag_record fr = { FR_GLOBAL | FR_CHAN | FR_BOT, 0, 0, 0 };
   if (!chan)
   if (!chan)
     fr.match |= FR_ANYWH;
     fr.match |= FR_ANYWH;
@@ -529,6 +557,9 @@ dolimit(const struct chanset_t *chan)
   if (!chan)
   if (!chan)
     return 0;
     return 0;
 
 
+  if (chan->role & ROLE_LIMIT)
+    return 1;
+
   struct flag_record fr = { FR_GLOBAL | FR_CHAN | FR_BOT, 0, 0, 0 };
   struct flag_record fr = { FR_GLOBAL | FR_CHAN | FR_BOT, 0, 0, 0 };
 
 
   get_user_flagrec(conf.bot->u, &fr, chan->dname);
   get_user_flagrec(conf.bot->u, &fr, chan->dname);

+ 21 - 0
src/flags.h

@@ -58,6 +58,27 @@ enum deflag_event_t {
   DEFLAG_EVENT_MOP,
   DEFLAG_EVENT_MOP,
 };
 };
 
 
+struct rolecount {
+  const char* name;
+  short role;
+  int count;
+};
+
+extern struct rolecount role_counts[];
+
+#define ROLE_VOICE    BIT0
+#define ROLE_FLOOD    BIT1
+#define ROLE_OP       BIT2
+#define ROLE_DEOP     BIT3
+#define ROLE_KICK     BIT4
+#define ROLE_BAN      BIT5
+#define ROLE_TOPIC    BIT6
+#define ROLE_LIMIT    BIT7
+#define ROLE_RESOLV   BIT8
+#define ROLE_REVENGE  BIT9
+#define ROLE_CHANMODE BIT10
+#define ROLE_PROTECT  BIT11
+
 #define USER_DEFAULT	0
 #define USER_DEFAULT	0
 
 
 #define USER_ADMIN	FLAG[(int) 'a']
 #define USER_ADMIN	FLAG[(int) 'a']

+ 0 - 1
src/main.c

@@ -95,7 +95,6 @@ char	git_version[50] = "";
 
 
 bool	used_B = 0;		/* did we get started with -B? */
 bool	used_B = 0;		/* did we get started with -B? */
 bool	safe_to_log = 0;
 bool	safe_to_log = 0;
-int 	role;
 bool 	loading = 0;
 bool 	loading = 0;
 int	default_flags = 0;	/* Default user flags and */
 int	default_flags = 0;	/* Default user flags and */
 bool     have_linked_to_hub = 0;  /* Have we ever been linked to a hub? */
 bool     have_linked_to_hub = 0;  /* Have we ever been linked to a hub? */

+ 1 - 1
src/main.h

@@ -14,7 +14,7 @@ enum {
   CONF_STATIC
   CONF_STATIC
 };
 };
 
 
-extern int		role, default_flags, default_uflags, do_confedit,
+extern int		default_flags, default_uflags, do_confedit,
 			updating, do_restart, do_write_userfile;
 			updating, do_restart, do_write_userfile;
 extern bool		use_stderr, backgrd, used_B, term_z, loading, have_linked_to_hub, restart_was_update, restarting, safe_to_log;
 extern bool		use_stderr, backgrd, used_B, term_z, loading, have_linked_to_hub, restart_was_update, restarting, safe_to_log;
 extern char		tempdir[], *binname, owner[121], version[151], ver[101], quit_msg[], *socksfile;
 extern char		tempdir[], *binname, owner[121], version[151], ver[101], quit_msg[], *socksfile;

+ 16 - 0
src/mod/channels.mod/chanmisc.c

@@ -971,6 +971,14 @@ static void init_channel(struct chanset_t *chan, bool reset)
   chan->channel.floodtime = new bd::HashTable<bd::String, bd::HashTable<flood_t, time_t> >;
   chan->channel.floodtime = new bd::HashTable<bd::String, bd::HashTable<flood_t, time_t> >;
   chan->channel.floodnum  = new bd::HashTable<bd::String, bd::HashTable<flood_t, int> >;
   chan->channel.floodnum  = new bd::HashTable<bd::String, bd::HashTable<flood_t, int> >;
   chan->channel.cached_members = new bd::HashTable<bd::String, memberlist*>;
   chan->channel.cached_members = new bd::HashTable<bd::String, memberlist*>;
+  /* Don't clear out existing roles, keep them until rebalancing
+   * to not create a window of missing roles. */
+  if (!chan->bot_roles) {
+    chan->bot_roles = new bd::HashTable<bd::String, int>;
+    chan->role_bots = new bd::HashTable<short, bd::Array<bd::String> >;
+    chan->role = 0;
+  }
+  chan->needs_role_rebalance = 1;
 }
 }
 
 
 static void clear_masklist(masklist *m)
 static void clear_masklist(masklist *m)
@@ -1016,6 +1024,14 @@ void clear_channel(struct chanset_t *chan, bool reset)
   chan->channel.floodtime = NULL;
   chan->channel.floodtime = NULL;
   delete chan->channel.floodnum;
   delete chan->channel.floodnum;
   chan->channel.floodnum = NULL;
   chan->channel.floodnum = NULL;
+  /* Don't clear out existing roles if resetting, keep them until rebalancing
+   * to not create a window of missing roles. */
+  if (!reset) {
+    delete chan->bot_roles;
+    chan->bot_roles = NULL;
+    delete chan->role_bots;
+    chan->role_bots = NULL;
+  }
 
 
   if (chan->channel.cached_members) {
   if (chan->channel.cached_members) {
     if (chan->channel.cached_members->size()) {
     if (chan->channel.cached_members->size()) {

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

@@ -290,12 +290,6 @@ static void got_down(char *botnick, char *code, char *par)
   add_mode(chan, '-', 'o', botname);
   add_mode(chan, '-', 'o', botname);
 }
 }
 
 
-static void got_role(char *botnick, char *code, char *par)
-{
-  role = atoi(newsplit(&par));
-  putlog(LOG_DEBUG, "@", "Got role index %d", role);
-}
-
 void got_kl(char *botnick, char *code, char *par)
 void got_kl(char *botnick, char *code, char *par)
 {
 {
   killed_bots++;
   killed_bots++;
@@ -306,59 +300,6 @@ void got_kl(char *botnick, char *code, char *par)
   }
   }
 }
 }
 
 
-
-static void rebalance_roles()
-{
-  struct bot_addr *ba = NULL;
-  int r[5] = { 0, 0, 0, 0, 0 };
-  unsigned int hNdx, lNdx, i;
-  char tmp[10] = "";
-
-  for (i = 0; i < (unsigned) dcc_total; i++) {
-    if (dcc[i].type && dcc[i].user && dcc[i].user->bot && bot_hublevel(dcc[i].user) == 999) {
-      ba = (struct bot_addr *) get_user(&USERENTRY_BOTADDR, dcc[i].user);
-      if (ba && (ba->roleid > 0) && (ba->roleid < 5))
-        r[(ba->roleid - 1)]++;
-    }
-  }
-  /*
-     Find high & low
-     while (high-low) > 2
-     move from highNdx to lowNdx
-   */
-
-  hNdx = 0;
-  lNdx = 0;
-  for (i = 1; i <= 4; i++) {
-    if (r[i] < r[lNdx])
-      lNdx = i;
-    if (r[i] > r[hNdx])
-      hNdx = i;
-  }
-  while (r[hNdx] - r[lNdx] >= 2) {
-    for (i = 0; i < (unsigned) dcc_total; i++) {
-      if (dcc[i].type && dcc[i].user && dcc[i].user->bot && bot_hublevel(dcc[i].user) == 999) {
-        ba = (struct bot_addr *) get_user(&USERENTRY_BOTADDR, dcc[i].user);
-        if (ba && (ba->roleid == (hNdx + 1))) {
-          ba->roleid = lNdx + 1;
-          simple_snprintf(tmp, sizeof(tmp), "rl %d", lNdx + 1);
-          putbot(dcc[i].nick, tmp);
-        }
-      }
-    }
-    r[hNdx]--;
-    r[lNdx]++;
-    hNdx = 0;
-    lNdx = 0;
-    for (i = 1; i <= 4; i++) {
-      if (r[i] < r[lNdx])
-        lNdx = i;
-      if (r[i] > r[hNdx])
-        hNdx = i;
-    }
-  }
-}
-
 static int
 static int
 check_slowjoinpart(struct chanset_t *chan)
 check_slowjoinpart(struct chanset_t *chan)
 {
 {
@@ -712,6 +653,8 @@ void remove_channel(struct chanset_t *chan)
    if (chan->groups) {
    if (chan->groups) {
      delete(chan->groups);
      delete(chan->groups);
    }
    }
+   delete chan->bot_roles;
+   delete chan->role_bots;
    delete chan->channel.floodtime;
    delete chan->channel.floodtime;
    delete chan->channel.floodnum;
    delete chan->channel.floodnum;
    free(chan);
    free(chan);
@@ -893,7 +836,6 @@ cmd_t channels_bot[] = {
   {"cset",	"", 	(Function) got_cset,  	NULL, 0},
   {"cset",	"", 	(Function) got_cset,  	NULL, 0},
   {"cycle",	"", 	(Function) got_cycle, 	NULL, LEAF},
   {"cycle",	"", 	(Function) got_cycle, 	NULL, LEAF},
   {"down",	"", 	(Function) got_down,  	NULL, LEAF},
   {"down",	"", 	(Function) got_down,  	NULL, LEAF},
-  {"rl",	"", 	(Function) got_role,  	NULL, 0},
   {"kl",	"", 	(Function) got_kl,    	NULL, 0},
   {"kl",	"", 	(Function) got_kl,    	NULL, 0},
   {"sj",	"", 	(Function) got_sj,    	NULL, 0},
   {"sj",	"", 	(Function) got_sj,    	NULL, 0},
   {"sp",	"", 	(Function) got_sp,    	NULL, 0},
   {"sp",	"", 	(Function) got_sp,    	NULL, 0},
@@ -913,7 +855,6 @@ void channels_init()
 {
 {
   timer_create_secs(60, "check_expired_masks", (Function) check_expired_masks);
   timer_create_secs(60, "check_expired_masks", (Function) check_expired_masks);
   if (conf.bot->hub) {
   if (conf.bot->hub) {
-    timer_create_secs(30, "rebalance_roles", (Function) rebalance_roles);
     timer_create_secs(30, "check_should_close", (Function) check_should_close);
     timer_create_secs(30, "check_should_close", (Function) check_should_close);
 #ifdef G_BACKUP
 #ifdef G_BACKUP
     timer_create_secs(30, "check_should_backup", (Function) check_should_backup);
     timer_create_secs(30, "check_should_backup", (Function) check_should_backup);

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

@@ -900,12 +900,12 @@ static void refresh_ban_kick(struct chanset_t* chan, memberlist *m, const char *
   for (int cycle = 0; cycle < 2; cycle++) {
   for (int cycle = 0; cycle < 2; cycle++) {
     for (register maskrec* b = cycle ? chan->bans : global_bans; b; b = b->next) {
     for (register maskrec* b = cycle ? chan->bans : global_bans; b; b = b->next) {
       if (wild_match(b->mask, user) || match_cidr(b->mask, user)) {
       if (wild_match(b->mask, user) || match_cidr(b->mask, user)) {
-        if (role == 1 && chan_hasop(m))
+        if ((chan->role & ROLE_DEOP) && chan_hasop(m))
   	  add_mode(chan, '-', 'o', m->nick);	/* Guess it can't hurt.	*/
   	  add_mode(chan, '-', 'o', m->nick);	/* Guess it can't hurt.	*/
 	check_exemptlist(chan, user);
 	check_exemptlist(chan, user);
 	do_mask(chan, chan->channel.ban, b->mask, 'b');
 	do_mask(chan, chan->channel.ban, b->mask, 'b');
 	b->lastactive = now;
 	b->lastactive = now;
-        if (role == 2) {
+        if (chan->role & ROLE_KICK) {
           char c[512] = "";		/* The ban comment.	*/
           char c[512] = "";		/* The ban comment.	*/
 
 
           if (b->desc && b->desc[0] != '@')
           if (b->desc && b->desc[0] != '@')
@@ -1294,12 +1294,26 @@ void check_this_user(char *hand, int del, char *host)
     for (m = chan->channel.member; m && m->nick[0]; m = m->next) {
     for (m = chan->channel.member; m && m->nick[0]; m = m->next) {
       bool check_member = 0;
       bool check_member = 0;
       bool had_user = m->user ? 1 : 0;
       bool had_user = m->user ? 1 : 0;
+      struct userrec* u;
+      bool matches_hand = false;
+
       m->tried_getuser = 0;
       m->tried_getuser = 0;
       member_getuser(m);
       member_getuser(m);
-      struct userrec* u = m->user;
+      u = m->user;
+      if (u) {
+        matches_hand = (strcasecmp(u->handle, hand) == 0);
+      }
+
+      if (u && u->bot && matches_hand) {
+        /* Newly discovered bot, or deleted bot which fullfilled a role,
+         * need to rebalance. */
+        if (!del || (del && (*chan->bot_roles)[u->handle] != 0)) {
+          chan->needs_role_rebalance = 1;
+        }
+      }
       if (m->user && !had_user) // If a member is newly recognized, act on it
       if (m->user && !had_user) // If a member is newly recognized, act on it
         check_member = 1;
         check_member = 1;
-      else if (del != 2 && m->user && !strcasecmp(m->user->handle, hand)) { //general check / -user, match specified user
+      else if (del != 2 && m->user && matches_hand) { //general check / -user, match specified user
         check_member = 1;
         check_member = 1;
         if (del == 1)
         if (del == 1)
           u = NULL; // Pretend user doesn't exist when checking
           u = NULL; // Pretend user doesn't exist when checking
@@ -2726,6 +2740,10 @@ static int gotjoin(char *from, char *chname)
 	m->last = now;
 	m->last = now;
 	m->delay = 0L;
 	m->delay = 0L;
 	m->flags = (chan_hasop(m) ? WASOP : 0);
 	m->flags = (chan_hasop(m) ? WASOP : 0);
+        /* New bot available for roles, rebalance. */
+        if (is_bot(m->user)) {
+          chan->needs_role_rebalance = 1;
+        }
 	set_handle_laston(chan->dname, m->user, now);
 	set_handle_laston(chan->dname, m->user, now);
 //	m->flags |= STOPWHO;
 //	m->flags |= STOPWHO;
         irc_log(chan, "%s returned from netsplit", m->nick);
         irc_log(chan, "%s returned from netsplit", m->nick);
@@ -2788,6 +2806,10 @@ static int gotjoin(char *from, char *chname)
 	} else {
 	} else {
           irc_log(chan, "Join: %s (%s)", nick, uhost);
           irc_log(chan, "Join: %s (%s)", nick, uhost);
           detect_chan_flood(m, from, chan, FLOOD_JOIN);
           detect_chan_flood(m, from, chan, FLOOD_JOIN);
+          /* New bot available for roles, rebalance. */
+          if (is_bot(m->user)) {
+            chan->needs_role_rebalance = 1;
+          }
 	  set_handle_laston(chan->dname, m->user, now);
 	  set_handle_laston(chan->dname, m->user, now);
 	}
 	}
       }
       }
@@ -2937,6 +2959,10 @@ static int gotpart(char *from, char *msg)
       chan->ircnet_status &= ~(CHAN_PEND | CHAN_JOINING);
       chan->ircnet_status &= ~(CHAN_PEND | CHAN_JOINING);
       reset_chan_info(chan);
       reset_chan_info(chan);
     }
     }
+    /* This bot fullfilled a role, need to rebalance. */
+    if (u && u->bot && (*chan->bot_roles)[u->handle] != 0) {
+      chan->needs_role_rebalance = 1;
+    }
     set_handle_laston(chan->dname, u, now);
     set_handle_laston(chan->dname, u, now);
 
 
     if (m) {
     if (m) {
@@ -3012,7 +3038,7 @@ static int gotkick(char *from, char *origmsg)
     if (mv->user) {
     if (mv->user) {
       // Revenge kick clients that kick our bots
       // Revenge kick clients that kick our bots
       if (chan->revenge && !mv->is_me && m && m != mv && mv->user->bot && !(m->user && m->user->bot)) {
       if (chan->revenge && !mv->is_me && m && m != mv && mv->user->bot && !(m->user && m->user->bot)) {
-        if (role < 5 && !chan_sentkick(m) && me_op(chan)) {
+        if ((chan->role & ROLE_REVENGE) && !chan_sentkick(m) && me_op(chan)) {
           m->flags |= SENTKICK;
           m->flags |= SENTKICK;
           dprintf(DP_MODE_NEXT, "KICK %s %s :%s%s\r\n", chan->name, m->nick, kickprefix, response(RES_REVENGE));
           dprintf(DP_MODE_NEXT, "KICK %s %s :%s%s\r\n", chan->name, m->nick, kickprefix, response(RES_REVENGE));
         } else {
         } else {
@@ -3025,6 +3051,10 @@ static int gotkick(char *from, char *origmsg)
       }
       }
 
 
       set_handle_laston(chan->dname, mv->user, now);
       set_handle_laston(chan->dname, mv->user, now);
+      /* This bot fullfilled a role, need to rebalance. */
+      if (mv->user->bot && (*chan->bot_roles)[mv->user->handle] != 0) {
+        chan->needs_role_rebalance = 1;
+      }
     }
     }
     irc_log(chan, "%s was kicked by %s (%s)", s1, from, msg);
     irc_log(chan, "%s was kicked by %s (%s)", s1, from, msg);
     /* Kicked ME?!? the sods! */
     /* Kicked ME?!? the sods! */
@@ -3197,8 +3227,13 @@ static int gotquit(char *from, char *msg)
       member_getuser(m);
       member_getuser(m);
       u = m->user;
       u = m->user;
       if (u) {
       if (u) {
-        if (u->bot)
+        if (u->bot) {
           counter_clear(u->handle);
           counter_clear(u->handle);
+          /* This bot fullfilled a role, need to rebalance. */
+          if ((*chan->bot_roles)[u->handle] != 0) {
+            chan->needs_role_rebalance = 1;
+          }
+        }
         set_handle_laston(chan->dname, u, now); /* If you remove this, the bot will crash when the user record in question
         set_handle_laston(chan->dname, u, now); /* If you remove this, the bot will crash when the user record in question
 						   is removed/modified during the tcl binds below, and the users was on more
 						   is removed/modified during the tcl binds below, and the users was on more
 						   than one monitored channel */
 						   than one monitored channel */

+ 24 - 0
src/mod/irc.mod/cmdsirc.c

@@ -1421,6 +1421,29 @@ static void cmd_authed(int idx, char *par)
   Auth::TellAuthed(idx);
   Auth::TellAuthed(idx);
 }
 }
 
 
+static void cmd_roles(int idx, char *par)
+{
+  struct chanset_t* chan = NULL;
+  size_t roleidx;
+  int role;
+
+  chan = get_channel(idx, par);
+  if (!chan) {
+    return;
+  }
+
+  putlog(LOG_CMDS, "*", "#%s# (%s) roles", dcc[idx].nick, chan->dname);
+
+  dprintf(idx, "Roles for %s:\n", chan->dname);
+
+  /* Advertise roles */
+  for (roleidx = 0; role_counts[roleidx].name; roleidx++) {
+    role = role_counts[roleidx].role;
+    dprintf(idx, "  %-8s: %s\n", role_counts[roleidx].name,
+        static_cast<bd::String>((*chan->role_bots)[role].join(" ")).c_str());
+  }
+}
+
 static void cmd_channel(int idx, char *par)
 static void cmd_channel(int idx, char *par)
 {
 {
   struct chanset_t *chan = NULL;
   struct chanset_t *chan = NULL;
@@ -1972,6 +1995,7 @@ static cmd_t irc_dcc[] =
   {"resetbans",		"o|o",	 (Function) cmd_resetbans,	NULL, LEAF|AUTH},
   {"resetbans",		"o|o",	 (Function) cmd_resetbans,	NULL, LEAF|AUTH},
   {"resetexempts",	"o|o",	 (Function) cmd_resetexempts,	NULL, LEAF|AUTH},
   {"resetexempts",	"o|o",	 (Function) cmd_resetexempts,	NULL, LEAF|AUTH},
   {"resetinvites",	"o|o",	 (Function) cmd_resetinvites,	NULL, LEAF|AUTH},
   {"resetinvites",	"o|o",	 (Function) cmd_resetinvites,	NULL, LEAF|AUTH},
+  {"roles",		"o|o",	 (Function) cmd_roles,		NULL, LEAF},
   {"say",		"o|o",	 (Function) cmd_say,		NULL, LEAF},
   {"say",		"o|o",	 (Function) cmd_say,		NULL, LEAF},
   {"swhois",		"",	 (Function) cmd_swhois,		NULL, LEAF|AUTH},
   {"swhois",		"",	 (Function) cmd_swhois,		NULL, LEAF|AUTH},
   {"topic",		"o|o",	 (Function) cmd_topic,		NULL, LEAF|AUTH},
   {"topic",		"o|o",	 (Function) cmd_topic,		NULL, LEAF|AUTH},

+ 106 - 1
src/mod/irc.mod/irc.c

@@ -1313,6 +1313,7 @@ reset_chan_info(struct chanset_t *chan)
     send_chan_who(DP_MODE, chan, 1);
     send_chan_who(DP_MODE, chan, 1);
     /* clear_channel nuked the data...so */
     /* clear_channel nuked the data...so */
     dprintf(DP_HELP, "TOPIC %s\n", chan->name);//Topic is very low priority
     dprintf(DP_HELP, "TOPIC %s\n", chan->name);//Topic is very low priority
+    rebalance_roles_chan(chan);
   }
   }
 }
 }
 
 
@@ -1619,7 +1620,7 @@ check_expired_chanstuff(struct chanset_t *chan)
       request_op(chan);
       request_op(chan);
     }
     }
 
 
-    if (role == 3) {
+    if (chan->role & ROLE_CHANMODE) {
       recheck_channel_modes(chan);
       recheck_channel_modes(chan);
     }
     }
   }
   }
@@ -1753,6 +1754,109 @@ static void bot_release_nick (char *botnick, char *code, char *par) {
   release_nick(par);
   release_nick(par);
 }
 }
 
 
+static void rebalance_roles_chan(struct chanset_t* chan)
+{
+  bd::Array<bd::String> bots;
+  int *bot_bits;
+  short role;
+  size_t botcount, mappedbot, omappedbot, botidx, roleidx, rolecount;
+  struct flag_record fr = {FR_GLOBAL | FR_CHAN, 0, 0, 0 };
+  memberlist *m;
+
+  if (chan->needs_role_rebalance == 0) {
+    return;
+  }
+
+  if (channel_pending(chan) || !channel_active(chan) ||
+      !shouldjoin(chan) || (chan->channel.mode & CHANANON)) {
+    return;
+  }
+
+  /* Gather list of all bots in the channel. */
+  /* XXX: Keep this known in chan->bots */
+  for (m = chan->channel.member; m && m->nick[0]; m = m->next) {
+    if (!member_getuser(m) || !is_bot(m->user)) {
+      continue;
+    }
+
+    get_user_flagrec(m->user, &fr, chan->dname, chan);
+
+    /* Only consider bots that can be opped to be roled. */
+    if (!chk_op(fr, chan)) {
+      continue;
+    }
+    bots << m->user->handle;
+  }
+  botcount = bots.length();
+  if (botcount == 0)
+    return;
+  bot_bits = (int*)calloc(botcount, sizeof(bot_bits[0]));
+
+  for (roleidx = 0; role_counts[roleidx].name; roleidx++) {
+    /* Map this role to a bot */
+    omappedbot = mappedbot = roleidx % botcount;
+    rolecount = role_counts[roleidx].count;
+    role = role_counts[roleidx].role;
+
+    /* Does the mapped bot have the bit yet? If not, check next bot,
+     * on max restart at 0 but avoid looping back to start. */
+    while (rolecount > 0) {
+      if (!(bot_bits[mappedbot] & role)) {
+        bot_bits[mappedbot] |= role;
+        --rolecount;
+      }
+
+      /* Try next bot */
+      ++mappedbot;
+
+      /* Reached the end, wrap around. */
+      if (mappedbot == botcount) {
+        mappedbot = 0;
+      }
+      /* Reached original bot, cannot satisfy the role need. */
+      if (mappedbot == omappedbot) {
+        break;
+      }
+    }
+  }
+
+  /* Reset current bits */
+  chan->bot_roles->clear();
+  chan->role_bots->clear();
+
+  /* Take bitmask of assigned roles and apply to bots. */
+  for (botidx = 0; botidx < botcount; botidx++) {
+    if (bot_bits[botidx] != 0) {
+      (*chan->bot_roles)[bots[botidx]] = bot_bits[botidx];
+    }
+  }
+
+  /* Fill role_bots */
+  for (roleidx = 0; role_counts[roleidx].name; roleidx++) {
+    role = role_counts[roleidx].role;
+    /* Find all bots with this role */
+    for (botidx = 0; botidx < botcount; botidx++) {
+      if (bot_bits[botidx] & role) {
+        (*chan->role_bots)[role] << bots[botidx];
+      }
+    }
+  }
+
+  /* Set my own roles */
+  chan->role = (*chan->bot_roles)[conf.bot->nick];
+  free(bot_bits);
+  chan->needs_role_rebalance = 0;
+}
+
+static void rebalance_roles()
+{
+  struct chanset_t* chan = NULL;
+
+  for (chan = chanset; chan; chan = chan->next) {
+    rebalance_roles_chan(chan);
+  }
+}
+
 static cmd_t irc_bot[] = {
 static cmd_t irc_bot[] = {
   {"gi", "", (Function) getin_request, NULL, LEAF},
   {"gi", "", (Function) getin_request, NULL, LEAF},
   {"mr", "", (Function) mass_request, NULL, LEAF},
   {"mr", "", (Function) mass_request, NULL, LEAF},
@@ -1764,6 +1868,7 @@ void
 irc_init()
 irc_init()
 {
 {
   timer_create_secs(60, "irc_minutely", (Function) irc_minutely);
   timer_create_secs(60, "irc_minutely", (Function) irc_minutely);
+  timer_create_secs(10, "rebalance_roles", (Function) rebalance_roles);
 
 
   /* Add our commands to the imported tables. */
   /* Add our commands to the imported tables. */
   add_builtins("dcc", irc_dcc);
   add_builtins("dcc", irc_dcc);

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

@@ -75,6 +75,7 @@ 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);
 static void do_protect(struct chanset_t* chan, const char* reason);
+static void rebalance_roles_chan(struct chanset_t* chan);
 
 
 /* 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>

+ 32 - 49
src/mod/irc.mod/mode.c

@@ -678,7 +678,7 @@ got_deop(struct chanset_t *chan, memberlist *m, memberlist *mv, char *isserver)
            */
            */
           (reversing && (!chan_bitch(chan) || chk_op(victim, chan))) ||
           (reversing && (!chan_bitch(chan) || chk_op(victim, chan))) ||
           /* Reop bots to avoid them needing to ask */
           /* Reop bots to avoid them needing to ask */
-          (role == 3 && mv->user && mv->user->bot && chk_op(victim, chan))
+          ((chan->role & ROLE_PROTECT) && mv->user && mv->user->bot && chk_op(victim, chan))
         )
         )
        ) {
        ) {
       /* Then we'll bless the victim */
       /* Then we'll bless the victim */
@@ -716,7 +716,7 @@ got_deop(struct chanset_t *chan, memberlist *m, memberlist *mv, char *isserver)
   } else {
   } else {
     // Revenge kick clients that deop our bots
     // Revenge kick clients that deop our bots
     if (chan->revenge && m && m != mv && mv->user && mv->user->bot && !(m->user && m->user->bot)) {
     if (chan->revenge && m && m != mv && mv->user && mv->user->bot && !(m->user && m->user->bot)) {
-      if (role < 5 && !chan_sentkick(m) && me_op(chan)) {
+      if ((chan->role & ROLE_REVENGE) && !chan_sentkick(m) && me_op(chan)) {
         m->flags |= SENTKICK;
         m->flags |= SENTKICK;
         dprintf(DP_MODE_NEXT, "KICK %s %s :%s%s\r\n", chan->name, m->nick, kickprefix, response(RES_REVENGE));
         dprintf(DP_MODE_NEXT, "KICK %s %s :%s%s\r\n", chan->name, m->nick, kickprefix, response(RES_REVENGE));
       } else {
       } else {
@@ -763,7 +763,7 @@ got_ban(struct chanset_t *chan, memberlist *m, char *mask, char *isserver)
 
 
   // Revenge kick clients that ban our bots
   // Revenge kick clients that ban our bots
   if (chan->revenge && m && matched_bot && !(m->user && m->user->bot)) {
   if (chan->revenge && m && matched_bot && !(m->user && m->user->bot)) {
-    if (role < 5 && !chan_sentkick(m)) {
+    if ((chan->role & ROLE_REVENGE) && !chan_sentkick(m)) {
       m->flags |= SENTKICK;
       m->flags |= SENTKICK;
       dprintf(DP_MODE_NEXT, "KICK %s %s :%s%s\r\n", chan->name, m->nick, kickprefix, response(RES_REVENGE));
       dprintf(DP_MODE_NEXT, "KICK %s %s :%s%s\r\n", chan->name, m->nick, kickprefix, response(RES_REVENGE));
     } else {
     } else {
@@ -1137,10 +1137,10 @@ gotmode(char *from, char *msg)
       if (me_op(chan)) {
       if (me_op(chan)) {
         char tmp[1024] = "";
         char tmp[1024] = "";
 
 
-        if (!isserver[0] && role && (!u || (u && !u->bot))) {
+        if (!isserver[0] && chan->role && (!u || (u && !u->bot))) {
           if (m && deops >= 3) {
           if (m && deops >= 3) {
             if (chan->mdop) {
             if (chan->mdop) {
-              if (role < 5 && !chan_sentkick(m)) {
+              if ((chan->role & ROLE_PROTECT) && !chan_sentkick(m)) {
                 m->flags |= SENTKICK;
                 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));
                 const size_t len = simple_snprintf(tmp, sizeof(tmp), "KICK %s %s :%s%s\r\n", chan->name, m->nick, kickprefix, response(RES_MASSDEOP));
                 dprintf_real(DP_MODE_NEXT, tmp, len, sizeof(tmp));
                 dprintf_real(DP_MODE_NEXT, tmp, len, sizeof(tmp));
@@ -1160,7 +1160,7 @@ gotmode(char *from, char *msg)
           if (ops >= 3) {
           if (ops >= 3) {
             if (chan->mop) {
             if (chan->mop) {
               if (m && !chan_sentkick(m)) {
               if (m && !chan_sentkick(m)) {
-                if (role < 5) {
+                if ((chan->role & ROLE_PROTECT)) {
                   m->flags |= SENTKICK;
                   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_MANUALOP));
                   const size_t len = simple_snprintf(tmp, sizeof(tmp), "KICK %s %s :%s%s\r\n", chan->name, m->nick, kickprefix, response(RES_MANUALOP));
                   dprintf_real(DP_MODE_NEXT, tmp, len, sizeof(tmp));
                   dprintf_real(DP_MODE_NEXT, tmp, len, sizeof(tmp));
@@ -1182,7 +1182,6 @@ gotmode(char *from, char *msg)
           }
           }
         }
         }
         if (ops) {
         if (ops) {
-          int n = 0;
           /* Check cookies */
           /* Check cookies */
           if (u && m && u->bot && !channel_fastop(chan) && !channel_take(chan) && !cookies_disabled) {
           if (u && m && u->bot && !channel_fastop(chan) && !channel_take(chan) && !cookies_disabled) {
             int isbadop = 0;
             int isbadop = 0;
@@ -1259,48 +1258,32 @@ gotmode(char *from, char *msg)
           }
           }
 
 
           /* manop */
           /* manop */
-          if (chan->manop && u && !u->bot) {
-            n = i = 0;
-
-            switch (role) {
-              case 0:
-                break;
-              case 1:
-                if (m) {
-                  /* Kick opper */
-                  if (!chan_sentkick(m)) {
-                    const size_t len = simple_snprintf(tmp, sizeof(tmp), "KICK %s %s :%s%s\r\n", chan->name, m->nick, kickprefix, response(RES_MANUALOP));
-                    dprintf_real(DP_MODE_NEXT, tmp, len, sizeof(tmp));
-                    m->flags |= SENTKICK;
-                  }
-                  simple_snprintf(tmp, sizeof(tmp), "%s MODE %s %s", m->from, chan->dname, modes[modecnt - 1]);
-                  deflag_user(u, DEFLAG_EVENT_MANUALOP, tmp, chan);
-                }
-                break;
-              default:
-                /* KICK the opped */
-                n = role - 1;
-                i = 0;
-                while ((i < modecnt) && (n > 0)) {
-                  if (modes[i] && !strncmp(modes[i], "+o", 2))
-                    n--;
-                  if (n)
-                    i++;
-                }
-                if (!n) {
-                  for (i = 0; i < modecnt; i++) {
-                    if (msign == '+' && mmode == 'o' && !match_my_nick(mparam)) {
-                      mv = ismember(chan, mparam);
-                      if (!mv || !chan_sentkick(mv)) {
-                        if (mv)
-                          mv->flags |= SENTKICK;
-                        const size_t len = simple_snprintf(tmp, sizeof(tmp), "KICK %s %s :%s%s\r\n", chan->name, mparam, kickprefix, response(RES_MANUALOPPED));
-                        dprintf_real(DP_MODE_NEXT, tmp, len, sizeof(tmp));
-                      }
-                    }
-                  }
-                }
-            }
+	  if (chan->manop && u && !u->bot) {
+
+	    if (m && (chan->role & ROLE_PROTECT)) {
+	      /* Kick opper */
+	      if (!chan_sentkick(m)) {
+		const size_t len = simple_snprintf(tmp, sizeof(tmp), "KICK %s %s :%s%s\r\n", chan->name, m->nick, kickprefix, response(RES_MANUALOP));
+		dprintf_real(DP_MODE_NEXT, tmp, len, sizeof(tmp));
+		m->flags |= SENTKICK;
+	      }
+	      simple_snprintf(tmp, sizeof(tmp), "%s MODE %s %s", m->from, chan->dname, modes[modecnt - 1]);
+	      deflag_user(u, DEFLAG_EVENT_MANUALOP, tmp, chan);
+	    }
+
+	    if (chan->role & ROLE_KICK) {
+	      for (i = 0; i < modecnt; i++) {
+		if (msign == '+' && mmode == 'o' && !match_my_nick(mparam)) {
+		  mv = ismember(chan, mparam);
+		  if (!mv || !chan_sentkick(mv)) {
+		    if (mv)
+		      mv->flags |= SENTKICK;
+		    const size_t len = simple_snprintf(tmp, sizeof(tmp), "KICK %s %s :%s%s\r\n", chan->name, mparam, kickprefix, response(RES_MANUALOPPED));
+		    dprintf_real(DP_MODE_NEXT, tmp, len, sizeof(tmp));
+		  }
+		}
+	      }
+	    }
           }
           }
         }
         }
       }
       }

+ 0 - 1
src/users.h

@@ -58,7 +58,6 @@ struct laston_info {
 };
 };
 
 
 struct bot_addr {
 struct bot_addr {
-  unsigned int roleid;
   char *address;
   char *address;
   char *uplink;
   char *uplink;
   unsigned short hublevel;
   unsigned short hublevel;