Fix desync when an oplevel deop is bounced (#1298140).
[ircu2.10.12-pk.git] / ircd / channel.c
index 4d99df794f0969b78505db561bcb4b29e9bb5241..0b0fb0e857ef53c04121a0f61c9be6fd6598dd59 100644 (file)
@@ -380,12 +380,11 @@ struct Ban *find_ban(struct Client *cptr, struct Ban *banlist)
       continue;
     /* Compare host portion of ban. */
     hostmask = banlist->banstr + banlist->nu_len + 1;
-    if (((banlist->flags & BAN_IPMASK)
-         ? !ipmask_check(&cli_ip(cptr), &banlist->address, banlist->addrbits)
-         : match(hostmask, iphost))
+    if (!((banlist->flags & BAN_IPMASK)
+         && ipmask_check(&cli_ip(cptr), &banlist->address, banlist->addrbits))
         && match(hostmask, cli_user(cptr)->host)
-        && !(sr && match(hostmask, sr) == 0))
-      continue;
+        && !(sr && !match(hostmask, sr)))
+        continue;
     /* If an exception matches, no ban can match. */
     if (banlist->flags & BAN_EXCEPTION)
       return NULL;
@@ -447,7 +446,7 @@ void add_user_to_channel(struct Channel* chptr, struct Client* who,
     member->user         = who;
     member->channel      = chptr;
     member->status       = flags;
-    member->oplevel      = oplevel;
+    SetOpLevel(member, oplevel);
 
     member->next_member  = chptr->members;
     if (member->next_member)
@@ -1182,101 +1181,6 @@ static void send_ban_list(struct Client* cptr, struct Channel* chptr)
   send_reply(cptr, RPL_ENDOFBANLIST, chptr->chname);
 }
 
-/** Check a key against a keyring.
- * We are now treating the key part of /join channellist key as a key
- * ring; that is, we try one key against the actual channel key, and if that
- * doesn't work, we try the next one, and so on. -Kev -Texaco
- * Returns: 0 on match, 1 otherwise
- * This version contributed by SeKs \<intru@info.polymtl.ca\>
- *
- * @param key          Key to check
- * @param keyring      Comma separated list of keys
- *
- * @returns True if the key was found and matches, false otherwise.
- */
-static int compall(char *key, char *keyring)
-{
-  char *p1;
-
-top:
-  p1 = key;                     /* point to the key... */
-  while (*p1 && *p1 == *keyring)
-  {                             /* step through the key and ring until they
-                                   don't match... */
-    p1++;
-    keyring++;
-  }
-
-  if (!*p1 && (!*keyring || *keyring == ','))
-    /* ok, if we're at the end of the and also at the end of one of the keys
-       in the keyring, we have a match */
-    return 0;
-
-  if (!*keyring)                /* if we're at the end of the key ring, there
-                                   weren't any matches, so we return 1 */
-    return 1;
-
-  /* Not at the end of the key ring, so step
-     through to the next key in the ring: */
-  while (*keyring && *(keyring++) != ',');
-
-  goto top;                     /* and check it against the key */
-}
-
-/** Returns if a user can join a channel with a specific key.
- *
- * @param sptr The client trying to join
- * @param chptr        The channel to join
- * @param key  The key to use
- *
- * @returns any error that occurred bit-wise OR'd with MAGIC_OPER_OVERRIDE
- *         if the oper used the magic key, 0 if no error occurred.
- */
-int can_join(struct Client *sptr, struct Channel *chptr, char *key)
-{
-  int overrideJoin = 0;  
-  
-  /*
-   * Now a banned user CAN join if invited -- Nemesi
-   * Now a user CAN escape channel limit if invited -- bfriendly
-   * Now a user CAN escape anything if invited -- Isomer
-   */
-
-  if (IsInvited(sptr, chptr))
-    return 0;
-  
-  /* An oper can force a join on a local channel using "OVERRIDE" as the key. 
-     a HACK(4) notice will be sent if he would not have been supposed
-     to join normally. */ 
-  if (IsLocalChannel(chptr->chname) && HasPriv(sptr, PRIV_WALK_LCHAN) &&
-      !BadPtr(key) && compall("OVERRIDE",chptr->mode.key) != 0 &&
-      compall("OVERRIDE",key) == 0)
-    overrideJoin = MAGIC_OPER_OVERRIDE;
-
-  if (chptr->mode.mode & MODE_INVITEONLY)
-       return overrideJoin + ERR_INVITEONLYCHAN;
-       
-  if (chptr->mode.limit && chptr->users >= chptr->mode.limit)
-       return overrideJoin + ERR_CHANNELISFULL;
-
-  if ((chptr->mode.mode & MODE_REGONLY) && !IsAccount(sptr))
-       return overrideJoin + ERR_NEEDREGGEDNICK;
-       
-  if (find_ban(sptr, chptr->banlist))
-       return overrideJoin + ERR_BANNEDFROMCHAN;
-  
-  /*
-   * now using compall (above) to test against a whole key ring -Kev
-   */
-  if (*chptr->mode.key && (EmptyString(key) || compall(chptr->mode.key, key)))
-    return overrideJoin + ERR_BADCHANNELKEY;
-
-  if (overrideJoin)    
-       return ERR_DONTCHEAT;
-       
-  return 0;
-}
-
 /** Remove bells and commas from channel name
  *
  * @param cn   Channel name to clean, modified in place.
@@ -1670,11 +1574,11 @@ modebuf_flush_int(struct ModeBuf *mbuf, int all)
     if (MB_TYPE(mbuf, i) & (MODE_CHANOP | MODE_VOICE)) {
       tmp = strlen(cli_name(MB_CLIENT(mbuf, i)));
 
-      if ((totalbuflen - IRCD_MAX(5, tmp)) <= 0) /* don't overflow buffer */
+      if ((totalbuflen - IRCD_MAX(9, tmp)) <= 0) /* don't overflow buffer */
        MB_TYPE(mbuf, i) |= MODE_SAVE; /* save for later */
       else {
        bufptr[(*bufptr_i)++] = MB_TYPE(mbuf, i) & MODE_CHANOP ? 'o' : 'v';
-       totalbuflen -= IRCD_MAX(5, tmp) + 1;
+       totalbuflen -= IRCD_MAX(9, tmp) + 1;
       }
     } else if (MB_TYPE(mbuf, i) & (MODE_BAN | MODE_APASS | MODE_UPASS)) {
       tmp = strlen(MB_STRING(mbuf, i));
@@ -1844,8 +1748,17 @@ modebuf_flush_int(struct ModeBuf *mbuf, int all)
        strptr_i = &remstr_i;
       }
 
-      /* deal with modes that take clients */
-      if (MB_TYPE(mbuf, i) & (MODE_CHANOP | MODE_VOICE))
+      /* 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)
+          && MB_OPLEVEL(mbuf, i) < MAXOPLEVEL)
+          *strptr_i += ircd_snprintf(0, strptr + *strptr_i, BUFSIZE - *strptr_i,
+                                     " %s%s:%d",
+                                     NumNick(MB_CLIENT(mbuf, i)),
+                                     MB_OPLEVEL(mbuf, i));
+
+      /* deal with other modes that take clients */
+      else if (MB_TYPE(mbuf, i) & (MODE_CHANOP | MODE_VOICE))
        build_string(strptr, strptr_i, NumNick(MB_CLIENT(mbuf, i)), ' ');
 
       /* deal with modes that take strings */
@@ -2065,16 +1978,18 @@ modebuf_mode_string(struct ModeBuf *mbuf, unsigned int mode, char *string,
  * @param mbuf         The modebuf to append the mode to.
  * @param mode         The mode to append.
  * @param client       The client argument to append.
+ * @param oplevel       The oplevel the user had or will have
  */
 void
 modebuf_mode_client(struct ModeBuf *mbuf, unsigned int mode,
-                   struct Client *client)
+                   struct Client *client, int oplevel)
 {
   assert(0 != mbuf);
   assert(0 != (mode & (MODE_ADD | MODE_DEL)));
 
   MB_TYPE(mbuf, mbuf->mb_count) = mode;
   MB_CLIENT(mbuf, mbuf->mb_count) = client;
+  MB_OPLEVEL(mbuf, mbuf->mb_count) = oplevel;
 
   /* when we've reached the maximal count, flush the buffer */
   if (++mbuf->mb_count >=
@@ -2245,6 +2160,7 @@ struct ParseState {
   struct Ban banlist[MAXPARA];
   struct {
     unsigned int flag;
+    unsigned short oplevel;
     struct Client *client;
   } cli_change[MAXPARA];
 };
@@ -2337,14 +2253,24 @@ mode_parse_limit(struct ParseState *state, int *flag_p)
   }
 }
 
+/** Helper function to clean key-like parameters. */
+static void
+clean_key(char *s)
+{
+  int t_len = KEYLEN;
+
+  while (*s > ' ' && *s != ':' && *s != ',' && t_len--)
+    s++;
+  *s = '\0';
+}
+
 /*
  * Helper function to convert keys
  */
 static void
 mode_parse_key(struct ParseState *state, int *flag_p)
 {
-  char *t_str, *s;
-  int t_len;
+  char *t_str;
 
   if (MyUser(state->sptr) && state->max_args <= 0) /* drop if too many args */
     return;
@@ -2370,15 +2296,9 @@ mode_parse_key(struct ParseState *state, int *flag_p)
     return;
   state->done |= DONE_KEY;
 
-  t_len = KEYLEN;
-
   /* clean up the key string */
-  s = t_str;
-  while (*s > ' ' && *s != ':' && *s != ',' && t_len--)
-    s++;
-  *s = '\0';
-
-  if (!*t_str) { /* warn if empty */
+  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");
@@ -2418,10 +2338,11 @@ mode_parse_key(struct ParseState *state, int *flag_p)
     modebuf_mode_string(state->mbuf, state->dir | flag_p[0], t_str, 0);
 
   if (state->flags & MODE_PARSE_SET) {
-    if (state->dir == MODE_ADD) /* set the new key */
-      ircd_strncpy(state->chptr->mode.key, t_str, KEYLEN);
-    else /* remove the old key */
+    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)
+      ircd_strncpy(state->chptr->mode.key, t_str, KEYLEN);
   }
 }
 
@@ -2431,8 +2352,7 @@ mode_parse_key(struct ParseState *state, int *flag_p)
 static void
 mode_parse_upass(struct ParseState *state, int *flag_p)
 {
-  char *t_str, *s;
-  int t_len;
+  char *t_str;
 
   if (MyUser(state->sptr) && state->max_args <= 0) /* drop if too many args */
     return;
@@ -2467,10 +2387,8 @@ mode_parse_upass(struct ParseState *state, int *flag_p)
     if (*state->chptr->mode.apass) {
       send_reply(state->sptr, ERR_NOTMANAGER, state->chptr->chname,
                  state->chptr->chname);
-    } else if (TStime() - state->chptr->creationtime >= 171000) {
-      send_reply(state->sptr, ERR_NOMANAGER_LONG, state->chptr->chname);
     } else {
-      send_reply(state->sptr, ERR_NOMANAGER_SHORT, state->chptr->chname);
+      send_reply(state->sptr, ERR_NOMANAGER, state->chptr->chname);
     }
     return;
   }
@@ -2479,15 +2397,9 @@ mode_parse_upass(struct ParseState *state, int *flag_p)
     return;
   state->done |= DONE_UPASS;
 
-  t_len = PASSLEN + 1;
-
   /* clean up the upass string */
-  s = t_str;
-  while (*++s > ' ' && *s != ':' && --t_len)
-    ;
-  *s = '\0';
-
-  if (!*t_str) { /* warn if empty */
+  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");
@@ -2531,10 +2443,11 @@ mode_parse_upass(struct ParseState *state, int *flag_p)
     modebuf_mode_string(state->mbuf, state->dir | flag_p[0], t_str, 0);
 
   if (state->flags & MODE_PARSE_SET) {
-    if (state->dir == MODE_ADD) /* set the new upass */
-      ircd_strncpy(state->chptr->mode.upass, t_str, PASSLEN);
-    else /* remove the old upass */
+    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)
+      ircd_strncpy(state->chptr->mode.upass, t_str, KEYLEN);
   }
 }
 
@@ -2545,8 +2458,7 @@ static void
 mode_parse_apass(struct ParseState *state, int *flag_p)
 {
   struct Membership *memb;
-  char *t_str, *s;
-  int t_len;
+  char *t_str;
 
   if (MyUser(state->sptr) && state->max_args <= 0) /* drop if too many args */
     return;
@@ -2577,7 +2489,9 @@ mode_parse_apass(struct ParseState *state, int *flag_p)
   }
 
   /* Don't allow to change the Apass if the channel is older than 48 hours. */
-  if (TStime() - state->chptr->creationtime >= 172800 && !IsAnOper(state->sptr)) {
+  if (MyUser(state->sptr)
+      && TStime() - state->chptr->creationtime >= 172800
+      && !IsAnOper(state->sptr)) {
     send_reply(state->sptr, ERR_CHANSECURED, state->chptr->chname);
     return;
   }
@@ -2587,10 +2501,8 @@ mode_parse_apass(struct ParseState *state, int *flag_p)
     if (*state->chptr->mode.apass) {
       send_reply(state->sptr, ERR_NOTMANAGER, state->chptr->chname,
                  state->chptr->chname);
-    } else if (TStime() - state->chptr->creationtime >= 171000) {
-      send_reply(state->sptr, ERR_NOMANAGER_LONG, state->chptr->chname);
     } else {
-      send_reply(state->sptr, ERR_NOMANAGER_SHORT, state->chptr->chname);
+      send_reply(state->sptr, ERR_NOMANAGER, state->chptr->chname);
     }
     return;
   }
@@ -2599,15 +2511,9 @@ mode_parse_apass(struct ParseState *state, int *flag_p)
     return;
   state->done |= DONE_APASS;
 
-  t_len = PASSLEN + 1;
-
   /* clean up the apass string */
-  s = t_str;
-  while (*++s > ' ' && *s != ':' && --t_len)
-    ;
-  *s = '\0';
-
-  if (!*t_str) { /* warn if empty */
+  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");
@@ -2646,8 +2552,14 @@ 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)
+        ircd_strncpy(state->chptr->mode.apass, t_str, KEYLEN);
       /* Make it VERY clear to the user that this is a one-time password */
-      ircd_strncpy(state->chptr->mode.apass, t_str, PASSLEN);
       if (MyUser(state->sptr)) {
        send_reply(state->sptr, RPL_APASSWARN_SET, state->chptr->mode.apass);
        send_reply(state->sptr, RPL_APASSWARN_SECRET, state->chptr->chname,
@@ -2663,7 +2575,7 @@ mode_parse_apass(struct ParseState *state, int *flag_p)
       /* Revert everyone to MAXOPLEVEL. */
       for (memb = state->chptr->members; memb; memb = memb->next_member) {
         if (memb->status & MODE_CHANOP)
-          memb->oplevel = MAXOPLEVEL;
+          SetOpLevel(memb, MAXOPLEVEL);
       }
     }
   }
@@ -2929,6 +2841,8 @@ mode_parse_client(struct ParseState *state, int *flag_p)
 {
   char *t_str;
   struct Client *acptr;
+  struct Membership *member;
+  int oplevel = MAXOPLEVEL + 1;
   int i;
 
   if (MyUser(state->sptr) && state->max_args <= 0) /* drop if too many args */
@@ -2949,8 +2863,13 @@ mode_parse_client(struct ParseState *state, int *flag_p)
 
   if (MyUser(state->sptr)) /* find client we're manipulating */
     acptr = find_chasing(state->sptr, t_str, NULL);
-  else
+  else {
+    if (t_str[5] == ':') {
+      t_str[5] = '\0';
+      oplevel = atoi(t_str + 6);
+    }
     acptr = findNUser(t_str);
+  }
 
   if (!acptr)
     return; /* find_chasing() already reported an error to the user */
@@ -2960,8 +2879,16 @@ mode_parse_client(struct ParseState *state, int *flag_p)
                                       state->cli_change[i].flag & flag_p[0]))
       break; /* found a slot */
 
+  /* If we are going to bounce this deop, mark the correct oplevel. */
+  if (state->flags & MODE_PARSE_BOUNCE
+      && state->dir == MODE_DEL
+      && flag_p[0] == MODE_CHANOP
+      && (member = find_member_link(state->chptr, acptr)))
+      oplevel = OpLevel(member);
+
   /* Store what we're doing to them */
   state->cli_change[i].flag = state->dir | flag_p[0];
+  state->cli_change[i].oplevel = oplevel;
   state->cli_change[i].client = acptr;
 }
 
@@ -3039,20 +2966,22 @@ mode_process_clients(struct ParseState *state)
                       "deop", equal ? "the same" : "a higher");
          continue;
        }
-    }
+      }
     }
 
     /* set op-level of member being opped */
     if ((state->cli_change[i].flag & (MODE_ADD | MODE_CHANOP)) ==
        (MODE_ADD | MODE_CHANOP)) {
-      /* If being opped by an outsider, get oplevel 0 for an apass
-       *   channel, else MAXOPLEVEL.
+      /* If a valid oplevel was specified, use it.
+       * Otherwise, if being opped by an outsider, get MAXOPLEVEL.
        * Otherwise, if not an apass channel, or state->member has
        *   MAXOPLEVEL, get oplevel MAXOPLEVEL.
        * Otherwise, get state->member's oplevel+1.
        */
-      if (!state->member)
-        SetOpLevel(member, state->chptr->mode.apass[0] ? 0 : MAXOPLEVEL);
+      if (state->cli_change[i].oplevel <= MAXOPLEVEL)
+        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
@@ -3075,7 +3004,8 @@ mode_process_clients(struct ParseState *state)
 
     /* accumulate the change */
     modebuf_mode_client(state->mbuf, state->cli_change[i].flag,
-                       state->cli_change[i].client);
+                       state->cli_change[i].client,
+                        state->cli_change[i].oplevel);
   } /* for (i = 0; state->cli_change[i].flags; i++) */
 }
 
@@ -3364,9 +3294,7 @@ joinbuf_join(struct JoinBuf *jbuf, struct Channel *chan, unsigned int flags)
   assert(0 != jbuf);
 
   if (!chan) {
-    if (jbuf->jb_type == JOINBUF_TYPE_JOIN)
-      sendcmdto_serv_butone(jbuf->jb_source, CMD_JOIN, jbuf->jb_connect, "0");
-
+    sendcmdto_serv_butone(jbuf->jb_source, CMD_JOIN, jbuf->jb_connect, "0");
     return;
   }