Quellcode durchsuchen

Merge branch '39-dynamic-roles'

* 39-dynamic-roles: (37 commits)
  Don't assign roles to split bots
  If a bot links without feature flags, share and save it as such.
  Can't rebalance roles in .reset until after WHO is done due to PENDING channel.
  Limit more actions per role.
  Record features in bot's own userrec when adding itself.
  Only consider bots for roles if they have the FEATURE_ROLES feature.
  Store the FFLAGS in the user file and cache it in the userrec.
  Send FEATURE_ROLES on link.
  Add basic infrastructure for tracking feature flags on the botlink.
  Consolidate some chk_autoop() logic.
  Split ROLE_VOICE into ROLE_OP and ROLE_INVITE
  Note roles in FEATURES
  Add more docs about roles
  cmd_roles: Add some sanity
  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
  ...
Bryan Drewery vor 8 Jahren
Ursprung
Commit
14e79e73bb

+ 1 - 0
FEATURES.md

@@ -49,6 +49,7 @@
  * Pre-defined list of kick reasons
  * Auto-limiter algorithm sets channel limit only when needed.
  * Configurable auto-voicer
+ * Bots automatically assign roles to manage channel (limit, voice, kicks, bans, etc)
  * [CIDR ban](http://svn.ratbox.org/svnroot/ircd-ratbox/trunk/doc/CIDR.txt) support
  * Bots DNS clients to see if they match users/bans. (+r)
  * Bots prefer requesting op from bots on same server or from a list of bots sorted by hops.

+ 8 - 0
doc/UPDATES.md

@@ -1,3 +1,11 @@
+* Wraith now automatically assigns roles to bots for channels, no longer
+  requiring manually assigning them with flags +flry for flood, limit,
+  resolve, auto-voice, auto-op. These roles are decentralized and per-chan
+  such that net-splits and botnet-splits and multiple groups in 1 chan
+  will properly assign roles out to bots to not cause overlap. Only leaf
+  bots know which bots have which roles. (#39)
+* Add cmd_roles (leaf only) to display roles for a channel. (#39)
+
 # master
   * Require C++11 compiler support (G++ 4.7+, clang32+)
 

+ 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.
  

+ 9 - 5
src/botcmd.cc

@@ -612,7 +612,7 @@ static void bot_unlink(int idx, char *par)
 static void bot_update(int idx, char *par)
 {
   char *bot = NULL, x, *vversion = NULL, *vcommit = NULL;
-  int vlocalhub = 0;
+  int vlocalhub = 0, fflags = -1;
   time_t vbuildts = 0L;
 
   bot = newsplit(&par);
@@ -630,9 +630,11 @@ static void bot_update(int idx, char *par)
     vcommit = newsplit(&par);
   if (par[0])
     vversion = newsplit(&par);
+  if (par[0])
+    fflags = atoi(newsplit(&par));
 
   if (in_chain(bot))
-    updatebot(idx, bot, x, vlocalhub, vbuildts, vcommit, vversion);
+    updatebot(idx, bot, x, vlocalhub, vbuildts, vcommit, vversion, fflags);
 }
 
 /* Newbot next share?
@@ -640,7 +642,7 @@ static void bot_update(int idx, char *par)
 static void bot_nlinked(int idx, char *par)
 {
   char *newbot = NULL, *next = NULL, *p = NULL, s[1024] = "", x = 0, *vversion = NULL, *vcommit = NULL;
-  int i, vlocalhub = 0;
+  int i, vlocalhub = 0, fflags = -1;
   time_t vbuildts = 0L;
   bool bogus = 0;
 
@@ -696,7 +698,9 @@ static void bot_nlinked(int idx, char *par)
     vcommit = newsplit(&par);
   if (par[0])
     vversion = newsplit(&par);
-  botnet_send_nlinked(idx, newbot, next, x, vlocalhub, vbuildts, vcommit, vversion);
+  if (par[0])
+    fflags = atoi(newsplit(&par));
+  botnet_send_nlinked(idx, newbot, next, x, vlocalhub, vbuildts, vcommit, vversion, fflags);
   
   if (x == '!') {
     if (conf.bot->hub)
@@ -705,7 +709,7 @@ static void bot_nlinked(int idx, char *par)
       chatout("*** %s linked to botnet.\n", newbot);
     x = '-';
   }
-  addbot(newbot, dcc[idx].nick, next, x, vlocalhub, vbuildts, vcommit, vversion ? vversion : (char *) "");
+  addbot(newbot, dcc[idx].nick, next, x, vlocalhub, vbuildts, vcommit, vversion ? vversion : (char *) "", fflags);
 }
 
 static void bot_unlinked(int idx, char *par)

+ 6 - 5
src/botmsg.cc

@@ -240,11 +240,11 @@ void botnet_send_unlinked(int idx, char *bot, char *args)
   }
 }
 
-void botnet_send_nlinked(int idx, char *bot, char *next, char flag, int vlocalhub, time_t vbuildts, char *vcommit, char *vversion)
+void botnet_send_nlinked(int idx, char *bot, char *next, char flag, int vlocalhub, time_t vbuildts, char *vcommit, char *vversion, int fflags)
 {
   if (tands > 0) {
-    const size_t len = simple_snprintf(OBUF, sizeof(OBUF), "n %s %s %cD0gc %d %d %s %s\n", bot, next, flag,
-                                       vlocalhub, (int) vbuildts, vcommit, vversion ? vversion : "");
+    const size_t len = simple_snprintf(OBUF, sizeof(OBUF), "n %s %s %cD0gc %d %d %s %s %d\n", bot, next, flag,
+                                       vlocalhub, (int) vbuildts, vcommit, vversion ? vversion : "", fflags);
     send_tand_but(idx, OBUF, len);
   }
 }
@@ -267,8 +267,9 @@ void botnet_send_update(int idx, tand_t * ptr)
 {
   if (tands > 0) {
     /* the D0gc is a lingering hack which probably will never be able to come out. */
-    const size_t len = simple_snprintf(OBUF, sizeof(OBUF), "u %s %cD0gc %d %d %s %s\n", ptr->bot, ptr->share, ptr->localhub,
-                                                          (int) ptr->buildts, ptr->commit, ptr->version);
+    const size_t len = simple_snprintf(OBUF, sizeof(OBUF), "u %s %cD0gc %d %d %s %s %d\n", ptr->bot, ptr->share, ptr->localhub,
+                                                          (int) ptr->buildts, ptr->commit, ptr->version,
+                                                          ptr->fflags);
     send_tand_but(idx, OBUF, len);
   }
 }

+ 18 - 40
src/botnet.cc

@@ -83,7 +83,7 @@ extern void counter_clear(const char* botnick);
 
 /* Add a tandem bot to our chain list
  */
-void addbot(char *who, char *from, char *next, char flag, int vlocalhub, time_t vbuildts, char *vcommit, char *vversion)
+void addbot(char *who, char *from, char *next, char flag, int vlocalhub, time_t vbuildts, char *vcommit, char *vversion, int fflags)
 {
   tand_t **ptr = &tandbot, *ptr2 = NULL;
 
@@ -109,6 +109,13 @@ void addbot(char *who, char *from, char *next, char flag, int vlocalhub, time_t
   ptr2->hub = is_hub(who);
   /* Cache user record */
   ptr2->u = userlist ? get_user_by_handle(userlist, who) : NULL;
+  ptr2->fflags = fflags;
+  if (fflags != -1) {
+    char buf[15];
+
+    simple_snprintf(buf, sizeof(buf), "%d", ptr2->fflags);
+    set_user(&USERENTRY_FFLAGS, ptr2->u ? ptr2->u : get_user_by_handle(userlist, who), buf);
+  }
   if (!strcasecmp(next, conf.bot->nick))
     ptr2->uplink = (tand_t *) 1;
   else
@@ -132,7 +139,7 @@ void check_should_backup()
 }
 #endif /* G_BACKUP */
 
-void updatebot(int idx, char *who, char share, int vlocalhub, time_t vbuildts, char *vcommit, char *vversion)
+void updatebot(int idx, char *who, char share, int vlocalhub, time_t vbuildts, char *vcommit, char *vversion, int fflags)
 {
   tand_t *ptr = findbot(who);
 
@@ -147,6 +154,15 @@ void updatebot(int idx, char *who, char share, int vlocalhub, time_t vbuildts, c
       strlcpy(ptr->commit, vcommit, sizeof(ptr->commit));
     if (vversion && vversion[0])
       strlcpy(ptr->version, vversion, 121);
+    /* -1 = unknown (do not modify) */
+    if (fflags != -1) {
+      char buf[15];
+
+      ptr->fflags = fflags;
+      simple_snprintf(buf, sizeof(buf), "%d", ptr->fflags);
+      set_user(&USERENTRY_FFLAGS, ptr->u ? ptr->u : get_user_by_handle(userlist, who), buf);
+    }
+    /* Assign flags here */
     botnet_send_update(idx, ptr);
   }
 }
@@ -1796,46 +1812,8 @@ 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);
 }
 
 /* vim: set sts=2 sw=2 ts=8 et: */

+ 2 - 2
src/botnet.h

@@ -21,8 +21,8 @@ void tell_bottree(int);
 void dump_links(int);
 int botlink(char *, int, char *);
 int botunlink(int, const char *, const char *);
-void addbot(char *, char *, char *, char, int, time_t, char *, char *);
-void updatebot(int, char *, char, int, time_t, char *, char *);
+void addbot(char *, char *, char *, char, int, time_t, char *, char *, int);
+void updatebot(int, char *, char, int, time_t, char *, char *, int);
 void rembot(const char *);
 tand_t *findbot(const char *);
 void unvia(int, struct tand_t_struct *);

+ 11 - 0
src/chan.h

@@ -282,6 +282,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 */

+ 5 - 1
src/chanprog.cc

@@ -498,7 +498,12 @@ static struct userrec* add_bot_userlist(char* bot) {
 }
 
 void add_myself_to_userlist() {
+  char buf[15];
+
   conf.bot->u = add_bot_userlist(conf.bot->nick);
+
+  simple_snprintf(buf, sizeof(buf), "%d", ALL_FEATURE_FLAGS);
+  set_user(&USERENTRY_FFLAGS, conf.bot->u, buf);
 }
 
 void add_child_bots() {
@@ -579,7 +584,6 @@ void chanprog()
 {
   struct utsname un;
 
-
   sdprintf("I am: %s", conf.bot->nick);
 
   /* Add the 'default' virtual channel.

+ 17 - 2
src/cmds.cc

@@ -2106,6 +2106,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);
@@ -2116,8 +2119,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")))

+ 19 - 6
src/dcc.cc

@@ -242,7 +242,9 @@ greet_new_bot(int idx)
     dcc[idx].status |= STAT_LEAF;
   dcc[idx].status |= STAT_LINKING;
 
-  dprintf(idx, "v 1001500 9 Wraith %s <%s> %d %li %s %s\n", egg_version, "-", conf.bot->localhub, (long)buildts, commit, egg_version);
+  dprintf(idx, "v 1001500 9 Wraith %s %d %d %li %s %s\n", egg_version,
+      conf.bot->u->fflags, conf.bot->localhub, (long)buildts, commit,
+      egg_version);
 
   for (int i = 0; i < dcc_total; i++) {
     if (dcc[i].type && dcc[i].type == &DCC_FORK_BOT) {
@@ -255,6 +257,8 @@ greet_new_bot(int idx)
 static void
 bot_version(int idx, char *par)
 {
+  char *work;
+
   dcc[idx].timeval = now;
   if (in_chain(dcc[idx].nick)) {
     dprintf(idx, "error Sorry, already connected.\n");
@@ -265,8 +269,6 @@ bot_version(int idx, char *par)
   }
 
   if ((par[0] >= '0') && (par[0] <= '9')) {
-    char *work = NULL;
-
     work = newsplit(&par);
     dcc[idx].u.bot->numver = atoi(work);
     /* old numver crap */
@@ -293,11 +295,22 @@ bot_version(int idx, char *par)
   char x[1024] = "", *vversion = NULL, *vcommit = NULL;
   int vlocalhub = -1;
   time_t vbuildts = 0;
+  int fflags = -1;
 
   strlcpy(dcc[idx].u.bot->version, par, 120);
   newsplit(&par);               /* 'ver' */
   newsplit(&par);               /* handlen */
-  newsplit(&par);               /* network */
+  /* fflags / (backward compat: network) */
+  if (par[0]) {
+    work = newsplit(&par);
+    /* Must support older bots which sent '<->' here for network. */
+    if (strcmp(work, "<->")) {
+      fflags = atoi(work);
+    } else {
+      /* Older bot doesn't have feature flags. */
+      fflags = 0;
+    }
+  }
   if (par[0])
     vlocalhub = atoi(newsplit(&par));
   if (par[0])
@@ -334,7 +347,7 @@ bot_version(int idx, char *par)
       dcc[idx].hub = 1;
     }
 
-    botnet_send_nlinked(idx, dcc[idx].nick, conf.bot->nick, '!', vlocalhub, vbuildts, vcommit, vversion);
+    botnet_send_nlinked(idx, dcc[idx].nick, conf.bot->nick, '!', vlocalhub, vbuildts, vcommit, vversion, fflags);
   } else {
         // This is now done in share_endstartup
         //have_linked_to_hub = 1;
@@ -346,7 +359,7 @@ bot_version(int idx, char *par)
 
   touch_laston(dcc[idx].user, "linked", now);
   dcc[idx].type = &DCC_BOT;
-  addbot(dcc[idx].nick, dcc[idx].nick, conf.bot->nick, '-', vlocalhub, vbuildts, vcommit, vversion);
+  addbot(dcc[idx].nick, dcc[idx].nick, conf.bot->nick, '-', vlocalhub, vbuildts, vcommit, vversion, fflags);
   simple_snprintf(x, sizeof x, "v 1001500");
   bot_share(idx, x);
   dprintf(idx, "el\n");

+ 3 - 2
src/eggdrop.h

@@ -144,6 +144,7 @@ enum {		/* TAKE A GUESS */
 #define have_cmd(name, flags) ((!is_restricted_cmd(name)) && (!(flags & (HUB|LEAF)) || (flags & HUB && conf.bot->hub) || (flags & LEAF && !conf.bot->hub)))
 
 
-#endif				/* _EGG_EGGDROP_H */
-
+#define FEATURE_ROLES		BIT0
+#define ALL_FEATURE_FLAGS	(FEATURE_ROLES)
 
+#endif				/* _EGG_EGGDROP_H */

+ 32 - 0
src/flags.cc

@@ -38,6 +38,23 @@
 
 flag_t FLAG[128];
 
+struct rolecount role_counts[] = {
+  {"voice",	ROLE_VOICE,	1},
+  {"flood",	ROLE_FLOOD,	3},
+  {"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},
+  {"invite",	ROLE_INVITE,	1},
+  {NULL,	0,		0},
+};
+
 void
 init_flags()
 {
@@ -488,6 +505,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 +522,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 +536,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 +558,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);

+ 22 - 0
src/flags.h

@@ -58,6 +58,28 @@ 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 ROLE_INVITE   BIT12
+
 #define USER_DEFAULT	0
 
 #define USER_ADMIN	FLAG[(int) 'a']

+ 0 - 1
src/main.cc

@@ -94,7 +94,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.cc

@@ -977,6 +977,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)
@@ -1019,6 +1027,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.cc

@@ -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;
    free(chan);
 }
 
@@ -891,7 +834,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},
@@ -911,7 +853,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);

+ 51 - 12
src/mod/irc.mod/chan.cc

@@ -554,7 +554,8 @@ static bool detect_chan_flood(memberlist* m, const char *from, struct chanset_t
   /* Do not punish non-existant channel members and IRC services like
    * ChanServ
    */
-  if (!chan || (which < 0) || (which >= FLOOD_CHAN_MAX) || !m)
+  if (!chan || (which < 0) || (which >= FLOOD_CHAN_MAX) || !m ||
+      !(chan->role & ROLE_FLOOD))
     return 0;
 
   /* Okay, make sure i'm not flood-checking myself */
@@ -892,12 +893,12 @@ static void refresh_ban_kick(struct chanset_t* chan, memberlist *m, const char *
   for (int cycle = 0; cycle < 2; cycle++) {
     for (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);	/* 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] != '@')
@@ -1219,7 +1220,7 @@ static void check_this_member(struct chanset_t *chan, memberlist *m,
        (!loading && userlist && chan_bitch(chan) && !chk_op(*fr, chan)) ) ) {
     /* if (target_priority(chan, m, 1)) */
       add_mode(chan, '-', 'o', m);
-  } else if (!chan_hasop(m) && dovoice(chan) && chk_autoop(m, *fr, chan)) {
+  } else if (!chan_hasop(m) && (chan->role & ROLE_OP) && chk_autoop(m, *fr, chan)) {
     do_op(m, chan, 1, 0);
   }
   if (dovoice(chan)) {
@@ -1283,12 +1284,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
@@ -1769,7 +1784,7 @@ static int got710(char *from, char *msg)
 
   chan = findchan(chname);
 
-  if (!chan->knock_flags || !dovoice(chan))
+  if (!chan->knock_flags || !(chan->role & ROLE_INVITE))
     return 0;
 
   struct userrec *u = get_user_by_host(uhost);
@@ -2125,6 +2140,7 @@ static int got315(char *from, char *msg)
     me->is_me = 1;
     if (!me->joined)
       me->joined = now;				/* set this to keep the whining masses happy */
+    rebalance_roles_chan(chan);
     if (me_op(chan))
       recheck_channel(chan, 2);
     else if (chan->channel.members == 1)
@@ -2716,6 +2732,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);
@@ -2775,6 +2795,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);
 	}
       }
@@ -2788,7 +2812,8 @@ static int gotjoin(char *from, char *chname)
         bool is_op = chk_op(fr, chan);
 
         /* Check for a mass join */
-        if (!splitjoin && chan->flood_mjoin_time && chan->flood_mjoin_thr && !is_op) {
+        if (chan->role & ROLE_FLOOD &&
+            !splitjoin && chan->flood_mjoin_time && chan->flood_mjoin_thr && !is_op) {
           if (chan->channel.drone_jointime < now - chan->flood_mjoin_time) {      //expired, reset counter
             chan->channel.drone_joins = 0;
           }
@@ -2810,7 +2835,8 @@ static int gotjoin(char *from, char *chname)
 	    u_match_mask(chan->invites, from))
 	  refresh_invite(chan, from);
 
-	if (!(use_exempts && (u_match_mask(global_exempts,from) || u_match_mask(chan->exempts, from)))) {
+	if (chan->role & ROLE_BAN &&
+            !(use_exempts && (u_match_mask(global_exempts,from) || u_match_mask(chan->exempts, from)))) {
           if (channel_enforcebans(chan) && !chan_sentkick(m) && !is_op &&
               !(use_exempts && (isexempted(chan, from) || (chan->ircnet_status & CHAN_ASKED_EXEMPTS)))) {
             for (masklist* b = chan->channel.ban; b->mask[0]; b = b->next) {
@@ -2859,7 +2885,7 @@ static int gotjoin(char *from, char *chname)
           /* Autoop */
           if (!chan_hasop(m) && 
                (op || 
-               (dovoice(chan) && chk_autoop(m, fr, chan)))) {
+               ((chan->role & ROLE_OP) && chk_autoop(m, fr, chan)))) {
             do_op(m, chan, 1, 0);
           }
 
@@ -2922,6 +2948,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) {
@@ -2997,7 +3027,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 {
@@ -3010,6 +3040,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! */
@@ -3175,8 +3209,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 */

+ 34 - 0
src/mod/irc.mod/cmdsirc.cc

@@ -1422,6 +1422,39 @@ 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);
+
+  if (!channel_active(chan)) {
+    dprintf(idx, "I'm not on %s right now!\n", chan->dname);
+    return;
+  }
+
+  if (chan->bot_roles->size() == 0) {
+    dprintf(idx, "Roles for %s are not yet calculated.\n", chan->dname);
+    return;
+  }
+
+  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;
@@ -1973,6 +2006,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},

+ 112 - 4
src/mod/irc.mod/irc.cc

@@ -1585,16 +1585,16 @@ check_expired_chanstuff(struct chanset_t *chan)
       }
 
       if (im_opped) {
-        if (dovoice(chan) && !loading && !chan_hasop(m)) {      /* autovoice of +v users if bot is +y */
+        if ((chan->role & (ROLE_OP|ROLE_VOICE)) && !loading && !chan_hasop(m)) {      /* autovoice of +v users if bot is +y */
           get_user_flagrec(m->user, &fr, chan->dname, chan);
 
           /* Autoop */
-          if (!chan_sentop(m) && chk_autoop(m, fr, chan)) {
+          if ((chan->role & ROLE_OP) && !chan_sentop(m) && chk_autoop(m, fr, chan)) {
             do_op(m, chan, 0, 0);
           }
 
           /* +v or +voice */
-          if (!chan_hasvoice(m) && !chan_sentvoice(m)) {
+          if ((chan->role & ROLE_VOICE) && !chan_hasvoice(m) && !chan_sentvoice(m)) {
             member_getuser(m, 1);
 
             if (m->user) {
@@ -1625,7 +1625,7 @@ check_expired_chanstuff(struct chanset_t *chan)
       request_op(chan);
     }
 
-    if (role == 3) {
+    if (chan->role & ROLE_CHANMODE) {
       recheck_channel_modes(chan);
     }
   }
@@ -1759,6 +1759,113 @@ 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) || m->split) {
+      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;
+    }
+    /* Only consider bots that have the roles feature. */
+    if (!(m->user->fflags & FEATURE_ROLES)) {
+      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},
@@ -1770,6 +1877,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.cc

@@ -679,7 +679,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 */
@@ -717,7 +717,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 {
@@ -764,7 +764,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 {
@@ -1138,10 +1138,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));
@@ -1161,7 +1161,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));
@@ -1183,7 +1183,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;
@@ -1260,48 +1259,32 @@ gotmode(char *from, char *msg)
           }
 
           /* manop */
-          if (chan->manop && u && !u->bot) {
-            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));
+		  }
+		}
+	      }
+	    }
           }
         }
       }

+ 4 - 4
src/mod/share.mod/share.cc

@@ -929,7 +929,7 @@ share_ufyes(int idx, char *par)
         dcc[idx].u.bot->uff_flags |= UFF_CHDEFAULT;
 
     if (strstr(par, "stream")) {
-      updatebot(-1, dcc[idx].nick, '+', 0, 0, 0, NULL);
+      updatebot(-1, dcc[idx].nick, '+', 0, 0, 0, NULL, -1);
       /* Start up a tbuf to queue outgoing changes for this bot until the
        * userlist is done transferring.
        */
@@ -1445,7 +1445,7 @@ static void share_read_stream(int idx, bd::Stream& stream) {
   checkchans(1);                /* remove marked channels */
   var_parse_my_botset();
   reaffirm_owners();            /* Make sure my owners are +a   */
-  updatebot(-1, dcc[idx].nick, '+', 0, 0, 0, NULL);
+  updatebot(-1, dcc[idx].nick, '+', 0, 0, 0, NULL, -1);
   send_sysinfo();
 
   if (restarting && !keepnick) {
@@ -1555,7 +1555,7 @@ start_sending_users(int idx)
            i == DCCSEND_BADFN ? "BAD FILE" : i == DCCSEND_FEMPTY ? "EMPTY FILE" : "UNKNOWN REASON!");
     dcc[idx].status &= ~(STAT_SHARE | STAT_SENDING | STAT_AGGRESSIVE);
   } else {
-    updatebot(-1, dcc[idx].nick, '+', 0, 0, 0, NULL);
+    updatebot(-1, dcc[idx].nick, '+', 0, 0, 0, NULL, -1);
     dcc[idx].status |= STAT_SENDING;
     strlcpy(dcc[j].host, dcc[idx].nick, sizeof(dcc[j].host)); /* Store bot's nick */
     dprintf(idx, "s us %lu %d %lu\n", iptolong(getmyip()), dcc[j].port, dcc[j].u.xfer->length);
@@ -1580,7 +1580,7 @@ cancel_user_xfer(int idx, void *x)
   int i, j = -1;
   if (cancel_user_xfer_staylinked) {
     /* turn off sharing flag */
-    updatebot(-1, dcc[idx].nick, '-', 0, 0, 0, NULL);
+    updatebot(-1, dcc[idx].nick, '-', 0, 0, 0, NULL, -1);
   }
   flush_tbuf(dcc[idx].nick);
 

+ 2 - 1
src/tandem.h

@@ -13,6 +13,7 @@ typedef struct tand_t_struct {
   struct tand_t_struct *next;
   time_t buildts;
   int localhub;
+  int fflags;
   struct userrec* u;
   char commit[10];
   char bot[HANDLEN + 1];
@@ -58,7 +59,7 @@ void botnet_send_trace(int, char *, char *, char *);
 void botnet_send_unlink(int, char *, char *, char *, char *);
 void botnet_send_link(int, char *, char *, char *);
 void botnet_send_update(int, tand_t *);
-void botnet_send_nlinked(int, char *, char *, char, int, time_t, char *, char *);
+void botnet_send_nlinked(int, char *, char *, char, int, time_t, char *, char *, int);
 void botnet_send_reject(int, char *, char *, char *, char *, char *);
 void botnet_send_log(int, const char *, int, const char *, bool = 0);
 void botnet_send_zapf(int, const char *, const char *, const char *);

+ 44 - 0
src/userent.cc

@@ -62,6 +62,7 @@ void init_userent()
   add_entry_type(&USERENTRY_ADDED);
   add_entry_type(&USERENTRY_MODIFIED);
   add_entry_type(&USERENTRY_SET);
+  add_entry_type(&USERENTRY_FFLAGS);
 }
 
 void list_type_kill(struct list_type *t)
@@ -472,6 +473,49 @@ struct user_entry_type USERENTRY_ARCH = {
  "ARCH"
 };
 
+bool fflags_unpack(struct userrec *u, struct user_entry *e)
+{
+  bool ret = def_unpack(u, e);
+  /* Cache the value in the user record. */
+  u->fflags = atoi((char *) def_get(u, e));
+  return ret;
+}
+
+bool fflags_set(struct userrec *u, struct user_entry *e, void *buf)
+{
+  bool ret;
+
+  /* No need to share since it is sent over the tandem/botlink. */
+  noshare = 1;
+  ret = def_set(u, e, buf);
+  noshare = 0;
+  /* Cache the value in the user record. */
+  u->fflags = atoi((char *) def_get(u, e));
+  return ret;
+}
+
+bool fflags_gotshare(struct userrec *u, struct user_entry *e, char *data,
+    int idx)
+{
+  /* Don't let another bot dictate our features. */
+  if (u == conf.bot->u) {
+    return false;
+  }
+  return def_gotshare(u, e, data, idx);
+}
+
+struct user_entry_type USERENTRY_FFLAGS = {
+ 0,
+ fflags_gotshare,
+ fflags_unpack,
+ def_write_userfile,
+ def_kill,
+ def_get,
+ fflags_set,
+ botmisc_display,
+ "FFLAGS"
+};
+
 void stats_add(struct userrec *u, int islogin, int op)
 {
   if (!u || u->bot)

+ 4 - 3
src/users.h

@@ -49,8 +49,9 @@ struct user_entry_type {
 extern struct user_entry_type USERENTRY_COMMENT, USERENTRY_LASTON,
  USERENTRY_INFO, USERENTRY_BOTADDR, USERENTRY_HOSTS,
  USERENTRY_PASS, USERENTRY_STATS, USERENTRY_ADDED, USERENTRY_MODIFIED,
- USERENTRY_SET, USERENTRY_SECPASS, USERENTRY_USERNAME, USERENTRY_NODENAME, USERENTRY_OS,
- USERENTRY_PASS1, USERENTRY_ARCH, USERENTRY_OSVER;
+ USERENTRY_SET, USERENTRY_SECPASS, USERENTRY_USERNAME, USERENTRY_NODENAME,
+ USERENTRY_OS, USERENTRY_PASS1, USERENTRY_ARCH, USERENTRY_OSVER,
+ USERENTRY_FFLAGS;
 
 struct laston_info {
   time_t laston;
@@ -58,7 +59,6 @@ struct laston_info {
 };
 
 struct bot_addr {
-  unsigned int roleid;
   char *address;
   char *uplink;
   unsigned short hublevel;
@@ -127,6 +127,7 @@ struct userrec {
   struct chanuserrec *chanrec;
   struct userrec *next;
   flag_t flags;
+  int fflags;
   char handle[HANDLEN + 1];
   char bot;
 };