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

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 година
родитељ
комит
a066b8e5cd
17 измењених фајлова са 330 додато и 164 уклоњено
  1. 1 0
      doc/UPDATES
  2. 26 4
      doc/help.txt
  3. 0 38
      src/botnet.c
  4. 11 0
      src/chan.h
  5. 17 2
      src/cmds.c
  6. 31 0
      src/flags.c
  7. 21 0
      src/flags.h
  8. 0 1
      src/main.c
  9. 1 1
      src/main.h
  10. 16 0
      src/mod/channels.mod/chanmisc.c
  11. 2 61
      src/mod/channels.mod/channels.c
  12. 41 6
      src/mod/irc.mod/chan.c
  13. 24 0
      src/mod/irc.mod/cmdsirc.c
  14. 106 1
      src/mod/irc.mod/irc.c
  15. 1 0
      src/mod/irc.mod/irc.h
  16. 32 49
      src/mod/irc.mod/mode.c
  17. 0 1
      src/users.h

+ 1 - 0
doc/UPDATES

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

+ 26 - 4
doc/help.txt

@@ -1089,7 +1089,7 @@ See also: console, channels%{+m}, status%{-}
 :hub:groups
 ###  $bgroups$b [bot]
    Shows the list of groups and which bots are in them.
-
+ 
    Specify a bot to only show which groups it is in.
  
 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.
  
 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
 ###  $bsave$b
    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.
 [B]  $bfish-auto-keyx$b      Whether to automatically do a DH1010 FiSH Key exchange when
                          accepting users. This is only supported if using callerid.
-
+ 
 [B]  $bfish-paranoid$b       Whether to automatically re-keyexchange after every message. This
                          mitigates replay attacks.
-
+ 
 [S]  $baltchars$b            Define string of characters to cycle when generating alternative
                          nicks when nick is taken. Ie: _-`[].
 [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]
    Changes the channel's topic, assuming the bot is a chanop or the
    channel is not +t (uses your current console channel).
-
+ 
    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.
  

+ 0 - 38
src/botnet.c

@@ -1789,45 +1789,7 @@ void zapfbot(int 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)
 {
-  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 dname[81];               /* what the users know the channel as like !eggdev */
   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 */

+ 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)
 {
   char *cmd = NULL;
+  struct chanset_t* chan = NULL;
+  size_t roleidx;
+  bd::Array<bd::String> roles;
 
   if (!par[0]) 
     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);
   if (!cmd || (cmd && !strcmp(cmd, "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")))
     tell_netdebug(idx);
   if (!cmd || (cmd && !strcmp(cmd, "dns")))

+ 31 - 0
src/flags.c

@@ -38,6 +38,22 @@
 
 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
 init_flags()
 {
@@ -488,6 +504,9 @@ doresolv(const struct chanset_t *chan)
   if (!chan)
     return 0;
 
+  if (chan->role & ROLE_RESOLV)
+    return 1;
+
   struct flag_record fr = { FR_GLOBAL | FR_CHAN | FR_BOT, 0, 0, 0 };
 
   get_user_flagrec(conf.bot->u, &fr, chan->dname);
@@ -502,6 +521,9 @@ dovoice(const struct chanset_t *chan)
   if (!chan)
     return 0;
 
+  if (chan->role & ROLE_VOICE)
+    return 1;
+
   struct flag_record fr = { FR_GLOBAL | FR_CHAN | FR_BOT, 0, 0, 0 };
 
   get_user_flagrec(conf.bot->u, &fr, chan->dname);
@@ -513,6 +535,12 @@ dovoice(const struct chanset_t *chan)
 int
 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 };
   if (!chan)
     fr.match |= FR_ANYWH;
@@ -529,6 +557,9 @@ dolimit(const struct chanset_t *chan)
   if (!chan)
     return 0;
 
+  if (chan->role & ROLE_LIMIT)
+    return 1;
+
   struct flag_record fr = { FR_GLOBAL | FR_CHAN | FR_BOT, 0, 0, 0 };
 
   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,
 };
 
+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_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	safe_to_log = 0;
-int 	role;
 bool 	loading = 0;
 int	default_flags = 0;	/* Default user flags and */
 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
 };
 
-extern int		role, default_flags, default_uflags, do_confedit,
+extern int		default_flags, default_uflags, do_confedit,
 			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 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.floodnum  = new bd::HashTable<bd::String, bd::HashTable<flood_t, int> >;
   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)
@@ -1016,6 +1024,14 @@ void clear_channel(struct chanset_t *chan, bool reset)
   chan->channel.floodtime = NULL;
   delete chan->channel.floodnum;
   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->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);
 }
 
-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)
 {
   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
 check_slowjoinpart(struct chanset_t *chan)
 {
@@ -712,6 +653,8 @@ void remove_channel(struct chanset_t *chan)
    if (chan->groups) {
      delete(chan->groups);
    }
+   delete chan->bot_roles;
+   delete chan->role_bots;
    delete chan->channel.floodtime;
    delete chan->channel.floodnum;
    free(chan);
@@ -893,7 +836,6 @@ cmd_t channels_bot[] = {
   {"cset",	"", 	(Function) got_cset,  	NULL, 0},
   {"cycle",	"", 	(Function) got_cycle, 	NULL, LEAF},
   {"down",	"", 	(Function) got_down,  	NULL, LEAF},
-  {"rl",	"", 	(Function) got_role,  	NULL, 0},
   {"kl",	"", 	(Function) got_kl,    	NULL, 0},
   {"sj",	"", 	(Function) got_sj,    	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);
   if (conf.bot->hub) {
-    timer_create_secs(30, "rebalance_roles", (Function) rebalance_roles);
     timer_create_secs(30, "check_should_close", (Function) check_should_close);
 #ifdef G_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 (register maskrec* b = cycle ? chan->bans : global_bans; b; b = b->next) {
       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.	*/
 	check_exemptlist(chan, user);
 	do_mask(chan, chan->channel.ban, b->mask, 'b');
 	b->lastactive = now;
-        if (role == 2) {
+        if (chan->role & ROLE_KICK) {
           char c[512] = "";		/* The ban comment.	*/
 
           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) {
       bool check_member = 0;
       bool had_user = m->user ? 1 : 0;
+      struct userrec* u;
+      bool matches_hand = false;
+
       m->tried_getuser = 0;
       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
         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;
         if (del == 1)
           u = NULL; // Pretend user doesn't exist when checking
@@ -2726,6 +2740,10 @@ static int gotjoin(char *from, char *chname)
 	m->last = now;
 	m->delay = 0L;
 	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);
 //	m->flags |= STOPWHO;
         irc_log(chan, "%s returned from netsplit", m->nick);
@@ -2788,6 +2806,10 @@ static int gotjoin(char *from, char *chname)
 	} else {
           irc_log(chan, "Join: %s (%s)", nick, uhost);
           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);
 	}
       }
@@ -2937,6 +2959,10 @@ static int gotpart(char *from, char *msg)
       chan->ircnet_status &= ~(CHAN_PEND | CHAN_JOINING);
       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);
 
     if (m) {
@@ -3012,7 +3038,7 @@ static int gotkick(char *from, char *origmsg)
     if (mv->user) {
       // 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 (role < 5 && !chan_sentkick(m) && me_op(chan)) {
+        if ((chan->role & ROLE_REVENGE) && !chan_sentkick(m) && me_op(chan)) {
           m->flags |= SENTKICK;
           dprintf(DP_MODE_NEXT, "KICK %s %s :%s%s\r\n", chan->name, m->nick, kickprefix, response(RES_REVENGE));
         } else {
@@ -3025,6 +3051,10 @@ static int gotkick(char *from, char *origmsg)
       }
 
       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);
     /* Kicked ME?!? the sods! */
@@ -3197,8 +3227,13 @@ static int gotquit(char *from, char *msg)
       member_getuser(m);
       u = m->user;
       if (u) {
-        if (u->bot)
+        if (u->bot) {
           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
 						   is removed/modified during the tcl binds below, and the users was on more
 						   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);
 }
 
+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)
 {
   struct chanset_t *chan = NULL;
@@ -1972,6 +1995,7 @@ static cmd_t irc_dcc[] =
   {"resetbans",		"o|o",	 (Function) cmd_resetbans,	NULL, LEAF|AUTH},
   {"resetexempts",	"o|o",	 (Function) cmd_resetexempts,	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},
   {"swhois",		"",	 (Function) cmd_swhois,		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);
     /* clear_channel nuked the data...so */
     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);
     }
 
-    if (role == 3) {
+    if (chan->role & ROLE_CHANMODE) {
       recheck_channel_modes(chan);
     }
   }
@@ -1753,6 +1754,109 @@ static void bot_release_nick (char *botnick, char *code, char *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[] = {
   {"gi", "", (Function) getin_request, NULL, LEAF},
   {"mr", "", (Function) mass_request, NULL, LEAF},
@@ -1764,6 +1868,7 @@ void
 irc_init()
 {
   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_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 bool member_getuser(memberlist* m, bool act_on_lookup = 0);
 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
  * 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))) ||
           /* 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 */
@@ -716,7 +716,7 @@ got_deop(struct chanset_t *chan, memberlist *m, memberlist *mv, char *isserver)
   } else {
     // Revenge kick clients that deop our bots
     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;
         dprintf(DP_MODE_NEXT, "KICK %s %s :%s%s\r\n", chan->name, m->nick, kickprefix, response(RES_REVENGE));
       } else {
@@ -763,7 +763,7 @@ got_ban(struct chanset_t *chan, memberlist *m, char *mask, char *isserver)
 
   // Revenge kick clients that ban our bots
   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;
       dprintf(DP_MODE_NEXT, "KICK %s %s :%s%s\r\n", chan->name, m->nick, kickprefix, response(RES_REVENGE));
     } else {
@@ -1137,10 +1137,10 @@ gotmode(char *from, char *msg)
       if (me_op(chan)) {
         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 (chan->mdop) {
-              if (role < 5 && !chan_sentkick(m)) {
+              if ((chan->role & ROLE_PROTECT) && !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));
                 dprintf_real(DP_MODE_NEXT, tmp, len, sizeof(tmp));
@@ -1160,7 +1160,7 @@ gotmode(char *from, char *msg)
           if (ops >= 3) {
             if (chan->mop) {
               if (m && !chan_sentkick(m)) {
-                if (role < 5) {
+                if ((chan->role & ROLE_PROTECT)) {
                   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));
                   dprintf_real(DP_MODE_NEXT, tmp, len, sizeof(tmp));
@@ -1182,7 +1182,6 @@ gotmode(char *from, char *msg)
           }
         }
         if (ops) {
-          int n = 0;
           /* Check cookies */
           if (u && m && u->bot && !channel_fastop(chan) && !channel_take(chan) && !cookies_disabled) {
             int isbadop = 0;
@@ -1259,48 +1258,32 @@ gotmode(char *from, char *msg)
           }
 
           /* 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 {
-  unsigned int roleid;
   char *address;
   char *uplink;
   unsigned short hublevel;