Make OPMODE ignore ban count and length checks.
[ircu2.10.12-pk.git] / ircd / channel.c
index 99e5182e6df59427cf825313573f17bb60915c23..aae13e6e595cfd37babb8773212a6bd0f544393c 100644 (file)
@@ -832,6 +832,8 @@ void channel_modes(struct Client *cptr, char *mbuf, char *pbuf, int buflen,
     *mbuf++ = 'D';
   else if (MyUser(cptr) && (chptr->mode.mode & MODE_WASDELJOINS))
     *mbuf++ = 'd';
+  if (chptr->mode.mode & MODE_REGISTERED)
+    *mbuf++ = 'R';
   if (chptr->mode.limit) {
     *mbuf++ = 'l';
     ircd_snprintf(0, pbuf, buflen, "%u", chptr->mode.limit);
@@ -914,7 +916,7 @@ void send_channel_modes(struct Client *cptr, struct Channel *chptr)
   int                 opped_members_index = 0;
   struct Membership** opped_members = NULL;
   int                 last_oplevel = 0;
-  int                 feat_oplevels = (chptr->mode.apass[0]) != '\0';
+  int                 send_oplevels = 0;
 
   assert(0 != cptr);
   assert(0 != chptr); 
@@ -972,6 +974,9 @@ void send_channel_modes(struct Client *cptr, struct Channel *chptr)
            ++number_of_ops;
          else
            opped_members[opped_members_index++] = member;
+          /* We also send oplevels if anyone is below the weakest level.  */
+          if (OpLevel(member) < MAXOPLEVEL)
+            send_oplevels = 1;
        }
        /* Only handle the members with the flags that we are interested in. */
         if ((member->status & CHFL_VOICED_OR_OPPED) == current_flags[flag_cnt])
@@ -1012,7 +1017,7 @@ void send_channel_modes(struct Client *cptr, struct Channel *chptr)
            if (IsChanOp(member))       /* flag_cnt == 2 or 3 */
            {
               /* append the absolute value of the oplevel */
-              if (feat_oplevels)
+              if (send_oplevels)
                 loc += ircd_snprintf(0, tbuf + loc, sizeof(tbuf) - loc, "%u", last_oplevel = member->oplevel);
               else
                 tbuf[loc++] = 'o';
@@ -1021,7 +1026,7 @@ void send_channel_modes(struct Client *cptr, struct Channel *chptr)
            msgq_append(&me, mb, tbuf);
            new_mode = 0;
          }
-         else if (feat_oplevels && flag_cnt > 1 && last_oplevel != member->oplevel)
+         else if (send_oplevels && flag_cnt > 1 && last_oplevel != member->oplevel)
          {
            /*
             * This can't be the first member of a (continued) BURST
@@ -1219,34 +1224,6 @@ static void send_ban_list(struct Client* cptr, struct Channel* chptr)
   send_reply(cptr, RPL_ENDOFBANLIST, chptr->chname);
 }
 
-/** Remove bells and commas from channel name
- *
- * @param cn   Channel name to clean, modified in place.
- */
-void clean_channelname(char *cn)
-{
-  int i;
-
-  for (i = 0; cn[i]; i++) {
-    if (i >= IRCD_MIN(CHANNELLEN, feature_int(FEAT_CHANNELLEN))
-        || !IsChannelChar(cn[i])) {
-      cn[i] = '\0';
-      return;
-    }
-    if (IsChannelLower(cn[i])) {
-      cn[i] = ToLower(cn[i]);
-#ifndef FIXME
-      /*
-       * Remove for .08+
-       * toupper(0xd0)
-       */
-      if ((unsigned char)(cn[i]) == 0xd0)
-        cn[i] = (char) 0xf0;
-#endif
-    }
-  }
-}
-
 /** Get a channel block, creating if necessary.
  *  Get Channel block for chname (and allocate a new channel
  *  block, if it didn't exists before).
@@ -1537,6 +1514,7 @@ modebuf_flush_int(struct ModeBuf *mbuf, int all)
     MODE_NOPRIVMSGS,   'n',
     MODE_REGONLY,      'r',
     MODE_DELJOINS,      'D',
+    MODE_REGISTERED,   'R',
 /*  MODE_KEY,          'k', */
 /*  MODE_BAN,          'b', */
     MODE_LIMIT,                'l',
@@ -1805,9 +1783,8 @@ modebuf_flush_int(struct ModeBuf *mbuf, int all)
        strptr_i = &remstr_i;
       }
 
-      /* if we're changing oplevels we know the oplevel, pass it on */
-      if (mbuf->mb_channel->mode.apass[0]
-          && (MB_TYPE(mbuf, i) & MODE_CHANOP)
+      /* if we're changing oplevels and we know the oplevel, pass it on */
+      if ((MB_TYPE(mbuf, i) & MODE_CHANOP)
           && MB_OPLEVEL(mbuf, i) < MAXOPLEVEL)
           *strptr_i += ircd_snprintf(0, strptr + *strptr_i, BUFSIZE - *strptr_i,
                                      " %s%s:%d",
@@ -1849,9 +1826,9 @@ modebuf_flush_int(struct ModeBuf *mbuf, int all)
                            addbuf, remstr, addstr);
     } else if (mbuf->mb_dest & MODEBUF_DEST_BOUNCE) {
       /*
-       * If HACK2 was set, we're bouncing; we send the MODE back to the
-       * connection we got it from with the senses reversed and a TS of 0;
-       * origin is us
+       * If HACK2 was set, we're bouncing; we send the MODE back to
+       * the connection we got it from with the senses reversed and
+       * the proper TS; origin is us
        */
       sendcmdto_one(&me, CMD_MODE, mbuf->mb_connect, "%H %s%s%s%s%s%s %Tu",
                    mbuf->mb_channel, addbuf_i ? "-" : "", addbuf,
@@ -1859,21 +1836,14 @@ modebuf_flush_int(struct ModeBuf *mbuf, int all)
                    mbuf->mb_channel->creationtime);
     } else {
       /*
-       * We're propagating a normal MODE command to the rest of the network;
-       * we send the actual channel TS unless this is a HACK3 or a HACK4
+       * We're propagating a normal (or HACK3 or HACK4) MODE command
+       * to the rest of the network.  We send the actual channel TS.
        */
-      if (IsServer(mbuf->mb_source) || IsMe(mbuf->mb_source))
-       sendcmdto_serv_butone(mbuf->mb_source, CMD_MODE, mbuf->mb_connect,
-                             "%H %s%s%s%s%s%s %Tu", mbuf->mb_channel,
-                             rembuf_i ? "-" : "", rembuf, addbuf_i ? "+" : "",
-                             addbuf, remstr, addstr,
-                             (mbuf->mb_dest & MODEBUF_DEST_HACK4) ? 0 :
-                             mbuf->mb_channel->creationtime);
-      else
-       sendcmdto_serv_butone(mbuf->mb_source, CMD_MODE, mbuf->mb_connect,
-                             "%H %s%s%s%s%s%s", mbuf->mb_channel,
-                             rembuf_i ? "-" : "", rembuf, addbuf_i ? "+" : "",
-                             addbuf, remstr, addstr);
+      sendcmdto_serv_butone(mbuf->mb_source, CMD_MODE, mbuf->mb_connect,
+                            "%H %s%s%s%s%s%s %Tu", mbuf->mb_channel,
+                            rembuf_i ? "-" : "", rembuf, addbuf_i ? "+" : "",
+                            addbuf, remstr, addstr,
+                            mbuf->mb_channel->creationtime);
     }
   }
 
@@ -1958,7 +1928,7 @@ modebuf_mode(struct ModeBuf *mbuf, unsigned int mode)
 
   mode &= (MODE_ADD | MODE_DEL | MODE_PRIVATE | MODE_SECRET | MODE_MODERATED |
           MODE_TOPICLIMIT | MODE_INVITEONLY | MODE_NOPRIVMSGS | MODE_REGONLY |
-           MODE_DELJOINS | MODE_WASDELJOINS);
+           MODE_DELJOINS | MODE_WASDELJOINS | MODE_REGISTERED);
 
   if (!(mode & ~(MODE_ADD | MODE_DEL))) /* don't add empty modes... */
     return;
@@ -1988,7 +1958,7 @@ modebuf_mode_uint(struct ModeBuf *mbuf, unsigned int mode, unsigned int uint)
   assert(0 != mbuf);
   assert(0 != (mode & (MODE_ADD | MODE_DEL)));
 
-  if (mode == (MODE_LIMIT | ((mbuf->mb_dest & MODEBUF_DEST_BOUNCE) ? MODE_ADD : MODE_DEL))) {
+  if (mode == (MODE_LIMIT | MODE_DEL)) {
       mbuf->mb_rem |= mode;
       return;
   }
@@ -2054,6 +2024,20 @@ modebuf_mode_client(struct ModeBuf *mbuf, unsigned int mode,
     modebuf_flush_int(mbuf, 0);
 }
 
+/** Check a channel for join-delayed members.
+ * @param[in] chan Channel to search.
+ * @return Non-zero if any members are join-delayed; false if none are.
+ */
+static int
+find_delayed_joins(const struct Channel *chan)
+{
+  const struct Membership *memb;
+  for (memb = chan->members; memb; memb = memb->next_member)
+    if (IsDelayedJoin(memb))
+      return 1;
+  return 0;
+}
+
 /** The exported binding for modebuf_flush()
  *
  * @param mbuf The mode buffer to flush.
@@ -2063,19 +2047,22 @@ modebuf_mode_client(struct ModeBuf *mbuf, unsigned int mode,
 int
 modebuf_flush(struct ModeBuf *mbuf)
 {
-  struct Membership *memb;
-
-  /* Check if MODE_WASDELJOINS should be set */
-  if (!(mbuf->mb_channel->mode.mode & (MODE_DELJOINS | MODE_WASDELJOINS))
-      && (mbuf->mb_rem & MODE_DELJOINS)) {
-    for (memb = mbuf->mb_channel->members; memb; memb = memb->next_member) {
-      if (IsDelayedJoin(memb)) {
-          mbuf->mb_channel->mode.mode |= MODE_WASDELJOINS;
-          mbuf->mb_add |= MODE_WASDELJOINS;
-          mbuf->mb_rem &= ~MODE_WASDELJOINS;
-          break;
-      }
-    }
+  /* Check if MODE_WASDELJOINS should be set: */
+  /* Must be set if going -D and some clients are hidden */
+  if ((mbuf->mb_rem & MODE_DELJOINS)
+      && !(mbuf->mb_channel->mode.mode & (MODE_DELJOINS | MODE_WASDELJOINS))
+      && find_delayed_joins(mbuf->mb_channel)) {
+    mbuf->mb_channel->mode.mode |= MODE_WASDELJOINS;
+    mbuf->mb_add |= MODE_WASDELJOINS;
+    mbuf->mb_rem &= ~MODE_WASDELJOINS;
+  }
+  /* Must be cleared if +D is set */
+  if ((mbuf->mb_add & MODE_DELJOINS)
+      && ((mbuf->mb_channel->mode.mode & (MODE_WASDELJOINS | MODE_WASDELJOINS))
+          == (MODE_WASDELJOINS | MODE_WASDELJOINS))) {
+    mbuf->mb_channel->mode.mode &= ~MODE_WASDELJOINS;
+    mbuf->mb_add &= ~MODE_WASDELJOINS;
+    mbuf->mb_rem |= MODE_WASDELJOINS;
   }
 
   return modebuf_flush_int(mbuf, 1);
@@ -2101,6 +2088,7 @@ modebuf_extract(struct ModeBuf *mbuf, char *buf)
     MODE_KEY,          'k',
     MODE_APASS,                'A',
     MODE_UPASS,                'U',
+    MODE_REGISTERED,   'R',
 /*  MODE_BAN,          'b', */
     MODE_LIMIT,                'l',
     MODE_REGONLY,      'r',
@@ -2160,9 +2148,10 @@ modebuf_extract(struct ModeBuf *mbuf, char *buf)
   return;
 }
 
-/** Simple function to invalidate bans
+/** Simple function to invalidate a channel's ban cache.
  *
- * This function sets all bans as being valid.
+ * This function marks all members of the channel as being neither
+ * banned nor banned.
  *
  * @param chan The channel to operate on.
  */
@@ -2191,12 +2180,15 @@ mode_invite_clear(struct Channel *chan)
 
 /* What we've done for mode_parse so far... */
 #define DONE_LIMIT     0x01    /**< We've set the limit */
-#define DONE_KEY       0x02    /**< We've set the key */
+#define DONE_KEY_ADD   0x02    /**< We've set the key */
 #define DONE_BANLIST   0x04    /**< We've sent the ban list */
 #define DONE_NOTOPER   0x08    /**< We've sent a "Not oper" error */
 #define DONE_BANCLEAN  0x10    /**< We've cleaned bans... */
-#define DONE_UPASS     0x20    /**< We've set user pass */
-#define DONE_APASS     0x40    /**< We've set admin pass */
+#define DONE_UPASS_ADD 0x20    /**< We've set user pass */
+#define DONE_APASS_ADD 0x40    /**< We've set admin pass */
+#define DONE_KEY_DEL    0x80    /**< We've removed the key */
+#define DONE_UPASS_DEL  0x100   /**< We've removed the user pass */
+#define DONE_APASS_DEL  0x200   /**< We've removed the admin pass */
 
 struct ParseState {
   struct ModeBuf *mbuf;
@@ -2310,15 +2302,41 @@ mode_parse_limit(struct ParseState *state, int *flag_p)
   }
 }
 
-/** Helper function to clean key-like parameters. */
-static void
-clean_key(char *s)
+/** Helper function to validate key-like parameters.
+ *
+ * @param[in] state Parse state for feedback to user.
+ * @param[in] s Key to validate.
+ * @param[in] command String to pass for need_more_params() command.
+ * @return Zero on an invalid key, non-zero if the key was okay.
+ */
+static int
+is_clean_key(struct ParseState *state, char *s, char *command)
 {
-  int t_len = KEYLEN;
+  int ii;
 
-  while (*s > ' ' && *s != ':' && *s != ',' && t_len--)
-    s++;
-  *s = '\0';
+  if (s[0] == '\0') {
+    if (MyUser(state->sptr))
+      need_more_params(state->sptr, command);
+    return 0;
+  }
+  else if (s[0] == ':') {
+    if (MyUser(state->sptr))
+      send_reply(state->sptr, ERR_INVALIDKEY, state->chptr->chname);
+    return 0;
+  }
+  for (ii = 0; (ii <= KEYLEN) && (s[ii] != '\0'); ++ii) {
+    if ((unsigned char)s[ii] <= ' ' || s[ii] == ',') {
+      if (MyUser(state->sptr))
+        send_reply(state->sptr, ERR_INVALIDKEY, state->chptr->chname);
+      return 0;
+    }
+  }
+  if (ii > KEYLEN) {
+    if (MyUser(state->sptr))
+      send_reply(state->sptr, ERR_INVALIDKEY, state->chptr->chname);
+    return 0;
+  }
+  return 1;
 }
 
 /*
@@ -2349,18 +2367,24 @@ mode_parse_key(struct ParseState *state, int *flag_p)
     return;
   }
 
-  if (state->done & DONE_KEY) /* allow key to be set only once */
-    return;
-  state->done |= DONE_KEY;
+  /* allow removing and then adding key, but not adding and then removing */
+  if (state->dir == MODE_ADD)
+  {
+    if (state->done & DONE_KEY_ADD)
+      return;
+    state->done |= DONE_KEY_ADD;
+  }
+  else
+  {
+    if (state->done & (DONE_KEY_ADD | DONE_KEY_DEL))
+      return;
+    state->done |= DONE_KEY_DEL;
+  }
 
-  /* clean up the key string */
-  clean_key(t_str);
-  if (!*t_str || *t_str == ':') { /* warn if empty */
-    if (MyUser(state->sptr))
-      need_more_params(state->sptr, state->dir == MODE_ADD ? "MODE +k" :
-                      "MODE -k");
+  /* If the key is invalid, tell the user and bail. */
+  if (!is_clean_key(state, t_str, state->dir == MODE_ADD ? "MODE +k" :
+                    "MODE -k"))
     return;
-  }
 
   if (!state->mbuf)
     return;
@@ -2397,8 +2421,7 @@ mode_parse_key(struct ParseState *state, int *flag_p)
   if (state->flags & MODE_PARSE_SET) {
     if (state->dir == MODE_DEL) /* remove the old key */
       *state->chptr->mode.key = '\0';
-    else if (!state->chptr->mode.key[0]
-             || ircd_strcmp(t_str, state->chptr->mode.key) < 0)
+    else
       ircd_strncpy(state->chptr->mode.key, t_str, KEYLEN);
   }
 }
@@ -2452,18 +2475,24 @@ mode_parse_upass(struct ParseState *state, int *flag_p)
     return;
   }
 
-  if (state->done & DONE_UPASS) /* allow upass to be set only once */
-    return;
-  state->done |= DONE_UPASS;
+  /* allow removing and then adding upass, but not adding and then removing */
+  if (state->dir == MODE_ADD)
+  {
+    if (state->done & DONE_UPASS_ADD)
+      return;
+    state->done |= DONE_UPASS_ADD;
+  }
+  else
+  {
+    if (state->done & (DONE_UPASS_ADD | DONE_UPASS_DEL))
+      return;
+    state->done |= DONE_UPASS_DEL;
+  }
 
-  /* clean up the upass string */
-  clean_key(t_str);
-  if (!*t_str || *t_str == ':') { /* warn if empty */
-    if (MyUser(state->sptr))
-      need_more_params(state->sptr, state->dir == MODE_ADD ? "MODE +U" :
-                      "MODE -U");
+  /* If the Upass is invalid, tell the user and bail. */
+  if (!is_clean_key(state, t_str, state->dir == MODE_ADD ? "MODE +U" :
+                    "MODE -U"))
     return;
-  }
 
   if (!state->mbuf)
     return;
@@ -2492,6 +2521,13 @@ mode_parse_upass(struct ParseState *state, int *flag_p)
       !ircd_strcmp(state->chptr->mode.upass, t_str))
     return; /* no upass change */
 
+  /* Skip if this is a burst, we have a Upass already and the new Upass is
+   * after the old one alphabetically */
+  if ((state->flags & MODE_PARSE_BURST) &&
+      *(state->chptr->mode.upass) &&
+      ircd_strcmp(state->chptr->mode.upass, t_str) <= 0)
+    return;
+
   if (state->flags & MODE_PARSE_BOUNCE) {
     if (*state->chptr->mode.upass) /* reset old upass */
       modebuf_mode_string(state->mbuf, MODE_DEL | flag_p[0],
@@ -2504,8 +2540,7 @@ mode_parse_upass(struct ParseState *state, int *flag_p)
   if (state->flags & MODE_PARSE_SET) {
     if (state->dir == MODE_DEL) /* remove the old upass */
       *state->chptr->mode.upass = '\0';
-    else if (state->chptr->mode.upass[0] == '\0'
-             || ircd_strcmp(t_str, state->chptr->mode.upass) < 0)
+    else
       ircd_strncpy(state->chptr->mode.upass, t_str, KEYLEN);
   }
 }
@@ -2583,18 +2618,24 @@ mode_parse_apass(struct ParseState *state, int *flag_p)
     }
   }
 
-  if (state->done & DONE_APASS) /* allow apass to be set only once */
-    return;
-  state->done |= DONE_APASS;
+  /* allow removing and then adding apass, but not adding and then removing */
+  if (state->dir == MODE_ADD)
+  {
+    if (state->done & DONE_APASS_ADD)
+      return;
+    state->done |= DONE_APASS_ADD;
+  }
+  else
+  {
+    if (state->done & (DONE_APASS_ADD | DONE_APASS_DEL))
+      return;
+    state->done |= DONE_APASS_DEL;
+  }
 
-  /* clean up the apass string */
-  clean_key(t_str);
-  if (!*t_str || *t_str == ':') { /* warn if empty */
-    if (MyUser(state->sptr))
-      need_more_params(state->sptr, state->dir == MODE_ADD ? "MODE +A" :
-                      "MODE -A");
+  /* If the Apass is invalid, tell the user and bail. */
+  if (!is_clean_key(state, t_str, state->dir == MODE_ADD ? "MODE +A" :
+                    "MODE -A"))
     return;
-  }
 
   if (!state->mbuf)
     return;
@@ -2603,6 +2644,13 @@ mode_parse_apass(struct ParseState *state, int *flag_p)
       !ircd_strcmp(state->chptr->mode.apass, t_str))
     return; /* no apass change */
 
+  /* Skip if this is a burst, we have an Apass already and the new Apass is
+   * after the old one alphabetically */
+  if ((state->flags & MODE_PARSE_BURST) &&
+      *(state->chptr->mode.apass) &&
+      ircd_strcmp(state->chptr->mode.apass, t_str) <= 0)
+    return;
+
   if (state->flags & MODE_PARSE_BOUNCE) {
     if (*state->chptr->mode.apass) /* reset old apass */
       modebuf_mode_string(state->mbuf, MODE_DEL | flag_p[0],
@@ -2614,12 +2662,10 @@ mode_parse_apass(struct ParseState *state, int *flag_p)
 
   if (state->flags & MODE_PARSE_SET) {
     if (state->dir == MODE_ADD) { /* set the new apass */
-      /* Only accept the new apass if there is no current apass
-       * (e.g. when a user sets it) or the new one is "less" than the
-       * old (for resolving conflicts during burst).
-       */
-      if (state->chptr->mode.apass[0] == '\0'
-          || ircd_strcmp(t_str, state->chptr->mode.apass) < 0)
+      /* Only accept the new apass if there is no current apass or
+       * this is a BURST. */
+      if (state->chptr->mode.apass[0] == '\0' ||
+          (state->flags & MODE_PARSE_BURST))
         ircd_strncpy(state->chptr->mode.apass, t_str, KEYLEN);
       /* Make it VERY clear to the user that this is a one-time password */
       if (MyUser(state->sptr)) {
@@ -2682,8 +2728,12 @@ bmatch(struct Ban *old_ban, struct Ban *new_ban)
   old_ban->banstr[old_ban->nu_len] = new_ban->banstr[new_ban->nu_len] = '@';
   if (res)
     return res;
-  /* Compare the addresses. */
-  return !ipmask_check(&new_ban->address, &old_ban->address, old_ban->addrbits);
+  /* If the old ban's mask mismatches, cannot be a superset. */
+  if (!ipmask_check(&new_ban->address, &old_ban->address, old_ban->addrbits))
+    return 1;
+  /* Otherwise it depends on whether the old ban's text is a superset
+   * of the new. */
+  return mmatch(old_ban->banstr, new_ban->banstr);
 }
 
 /** Add a ban from a ban list and mark bans that should be removed
@@ -2707,9 +2757,9 @@ int apply_ban(struct Ban **banlist, struct Ban *newban, int do_free)
   assert(newban->flags & (BAN_ADD|BAN_DEL));
   if (newban->flags & BAN_ADD) {
     size_t totlen = 0;
-    /* If a less specific entry is found, fail.  */
+    /* If a less specific *active* entry is found, fail.  */
     for (ban = *banlist; ban; ban = ban->next) {
-      if (!bmatch(ban, newban)) {
+      if (!bmatch(ban, newban) && !(ban->flags & BAN_DEL)) {
         if (do_free)
           free_ban(newban);
         return 1;
@@ -2869,6 +2919,7 @@ mode_process_bans(struct ParseState *state)
        len -= banlen;
       } else {
        if (state->flags & MODE_PARSE_SET && MyUser(state->sptr) &&
+            !(state->mbuf->mb_dest & MODEBUF_DEST_OPMODE) &&
            (len > (feature_int(FEAT_AVBANLEN) * feature_int(FEAT_MAXBANS)) ||
             count > feature_int(FEAT_MAXBANS))) {
          send_reply(state->sptr, ERR_BANLISTFULL, state->chptr->chname,
@@ -2939,17 +2990,19 @@ mode_parse_client(struct ParseState *state, int *flag_p)
     if (colon != NULL) {
       *colon++ = '\0';
       req_oplevel = atoi(colon);
-      if (!(state->flags & MODE_PARSE_FORCE)
+      if (*flag_p == CHFL_VOICE || state->dir == MODE_DEL) {
+        /* Ignore the colon and its argument. */
+      } else if (!(state->flags & MODE_PARSE_FORCE)
           && state->member
           && (req_oplevel < OpLevel(state->member)
               || (req_oplevel == OpLevel(state->member)
                   && OpLevel(state->member) < MAXOPLEVEL)
-              || req_oplevel > MAXOPLEVEL))
+              || req_oplevel > MAXOPLEVEL)) {
         send_reply(state->sptr, ERR_NOTLOWEROPLEVEL,
                    t_str, state->chptr->chname,
                    OpLevel(state->member), req_oplevel, "op",
                    OpLevel(state->member) == req_oplevel ? "the same" : "a higher");
-      else if (req_oplevel <= MAXOPLEVEL)
+      else if (req_oplevel <= MAXOPLEVEL)
         oplevel = req_oplevel;
     }
     /* find client we're manipulating */
@@ -3076,8 +3129,8 @@ mode_process_clients(struct ParseState *state)
         SetOpLevel(member, state->cli_change[i].oplevel);
       else if (!state->member)
         SetOpLevel(member, MAXOPLEVEL);
-      else if (!state->chptr->mode.apass[0] || OpLevel(state->member) == MAXOPLEVEL)
-        SetOpLevel(member, MAXOPLEVEL);
+      else if (OpLevel(state->member) >= MAXOPLEVEL)
+          SetOpLevel(member, OpLevel(state->member));
       else
         SetOpLevel(member, OpLevel(state->member) + 1);
     }
@@ -3118,6 +3171,11 @@ mode_parse_mode(struct ParseState *state, int *flag_p)
   if (!state->mbuf)
     return;
 
+  /* Local users are not permitted to change registration status */
+  if (flag_p[0] == MODE_REGISTERED && !(state->flags & MODE_PARSE_FORCE) &&
+      MyUser(state->sptr))
+    return;
+
   if (state->dir == MODE_ADD) {
     state->add |= flag_p[0];
     state->del &= ~flag_p[0];
@@ -3129,10 +3187,6 @@ mode_parse_mode(struct ParseState *state, int *flag_p)
       state->add &= ~MODE_SECRET;
       state->del |= MODE_SECRET;
     }
-    if (flag_p[0] & MODE_DELJOINS) {
-      state->add &= ~MODE_WASDELJOINS;
-      state->del |= MODE_WASDELJOINS;
-    }
   } else {
     state->add &= ~flag_p[0];
     state->del |= flag_p[0];
@@ -3143,10 +3197,19 @@ mode_parse_mode(struct ParseState *state, int *flag_p)
         (state->add & (MODE_SECRET | MODE_PRIVATE)));
 }
 
-/*
+/**
  * This routine is intended to parse MODE or OPMODE commands and effect the
- * changes (or just build the bounce buffer).  We pass the starting offset
- * as a 
+ * changes (or just build the bounce buffer).
+ *
+ * \param[out] mbuf Receives parsed representation of mode change.
+ * \param[in] cptr Connection that sent the message to this server.
+ * \param[in] sptr Original source of the message.
+ * \param[in] chptr Channel whose modes are being changed.
+ * \param[in] parc Number of valid strings in \a parv.
+ * \param[in] parv Text arguments representing mode change, with the
+ *   zero'th element containing a string like "+m" or "-o".
+ * \param[in] flags Set of bitwise MODE_PARSE_* flags.
+ * \param[in] member If non-null, the channel member attempting to change the modes.
  */
 int
 mode_parse(struct ModeBuf *mbuf, struct Client *cptr, struct Client *sptr,
@@ -3165,6 +3228,7 @@ mode_parse(struct ModeBuf *mbuf, struct Client *cptr, struct Client *sptr,
     MODE_KEY,          'k',
     MODE_APASS,                'A',
     MODE_UPASS,                'U',
+    MODE_REGISTERED,   'R',
     MODE_BAN,          'b',
     MODE_LIMIT,                'l',
     MODE_REGONLY,      'r',
@@ -3272,7 +3336,7 @@ mode_parse(struct ModeBuf *mbuf, struct Client *cptr, struct Client *sptr,
       state.parc--;
 
       /* is it a TS? */
-      if (IsServer(state.sptr) && !state.parc && IsDigit(*modestr)) {
+      if (IsServer(state.cptr) && !state.parc && IsDigit(*modestr)) {
        time_t recv_ts;
 
        if (!(state.flags & MODE_PARSE_SET))      /* don't set earlier TS if */
@@ -3282,6 +3346,37 @@ mode_parse(struct ModeBuf *mbuf, struct Client *cptr, struct Client *sptr,
 
        if (recv_ts && recv_ts < state.chptr->creationtime)
          state.chptr->creationtime = recv_ts; /* respect earlier TS */
+        else if (recv_ts > state.chptr->creationtime) {
+          struct Client *sserv;
+
+          /* Check whether the originating server has fully processed
+           * the burst to it. */
+          sserv = state.cptr;
+          if (!IsServer(sserv))
+              sserv = cli_user(sserv)->server;
+          if (IsBurstOrBurstAck(sserv)) {
+            /* This is a legal but unusual case; the source server
+             * probably just has not processed the BURST for this
+             * channel.  It SHOULD wipe out all its modes soon, so
+             * silently ignore the mode change rather than send a
+             * bounce that could desync modes from our side (that
+             * have already been sent).
+             */
+            state.mbuf->mb_add = 0;
+            state.mbuf->mb_rem = 0;
+            state.mbuf->mb_count = 0;
+            return state.args_used;
+          } else {
+            /* Server is desynced; bounce the mode and deop the source
+             * to fix it. */
+            state.flags &= ~MODE_PARSE_SET;
+            state.flags |= MODE_PARSE_BOUNCE;
+            state.mbuf->mb_dest &= ~(MODEBUF_DEST_CHANNEL | MODEBUF_DEST_HACK4);
+            state.mbuf->mb_dest |= MODEBUF_DEST_BOUNCE | MODEBUF_DEST_HACK2;
+            if (!IsServer(state.cptr))
+              state.mbuf->mb_dest |= MODEBUF_DEST_DEOP;
+          }
+        }
 
        break; /* break out of while loop */
       } else if (state.flags & MODE_PARSE_STRICT ||
@@ -3325,13 +3420,13 @@ mode_parse(struct ModeBuf *mbuf, struct Client *cptr, struct Client *sptr,
     if (state.chptr->mode.limit && !(state.done & DONE_LIMIT))
       modebuf_mode_uint(state.mbuf, MODE_DEL | MODE_LIMIT,
                        state.chptr->mode.limit);
-    if (*state.chptr->mode.key && !(state.done & DONE_KEY))
+    if (*state.chptr->mode.key && !(state.done & DONE_KEY_DEL))
       modebuf_mode_string(state.mbuf, MODE_DEL | MODE_KEY,
                          state.chptr->mode.key, 0);
-    if (*state.chptr->mode.upass && !(state.done & DONE_UPASS))
+    if (*state.chptr->mode.upass && !(state.done & DONE_UPASS_DEL))
       modebuf_mode_string(state.mbuf, MODE_DEL | MODE_UPASS,
                          state.chptr->mode.upass, 0);
-    if (*state.chptr->mode.apass && !(state.done & DONE_APASS))
+    if (*state.chptr->mode.apass && !(state.done & DONE_APASS_DEL))
       modebuf_mode_string(state.mbuf, MODE_DEL | MODE_APASS,
                          state.chptr->mode.apass, 0);
   }
@@ -3538,18 +3633,18 @@ void RevealDelayedJoin(struct Membership *member)
 
 void CheckDelayedJoins(struct Channel *chan)
 {
-  struct Membership *memb2;
-
-  if (chan->mode.mode & MODE_WASDELJOINS) {
-    for (memb2=chan->members;memb2;memb2=memb2->next_member)
-      if (IsDelayedJoin(memb2))
-        break;
-
-    if (!memb2) {
-      /* clear +d */
-      chan->mode.mode &= ~MODE_WASDELJOINS;
-      sendcmdto_channel_butserv_butone(&his, CMD_MODE, chan, NULL, 0,
-                                       "%H -d", chan);
-    }
+  if ((chan->mode.mode & MODE_WASDELJOINS) && !find_delayed_joins(chan)) {
+    chan->mode.mode &= ~MODE_WASDELJOINS;
+    sendcmdto_channel_butserv_butone(&his, CMD_MODE, chan, NULL, 0,
+                                     "%H -d", chan);
   }
 }
+
+/** Send a join for the user if (s)he is a hidden member of the channel.
+ */
+void RevealDelayedJoinIfNeeded(struct Client *sptr, struct Channel *chptr)
+{
+  struct Membership *member = find_member_link(chptr, sptr);
+  if (member && IsDelayedJoin(member))
+    RevealDelayedJoin(member);
+}