Make empty -A channels into zombies ("zannels") to avoid +A hijacks.
authorMichael Poole <mdpoole@troilus.org>
Mon, 31 Oct 2005 23:17:29 +0000 (23:17 +0000)
committerMichael Poole <mdpoole@troilus.org>
Mon, 31 Oct 2005 23:17:29 +0000 (23:17 +0000)
git-svn-id: file:///home/klmitch/undernet-ircu/undernet-ircu-svn/ircu2/branches/u2_10_12_branch@1537 c9e4aea6-c8fd-4c43-8297-357d70d61c8c

ChangeLog
ircd/channel.c
ircd/m_burst.c
ircd/m_join.c
ircd/m_mode.c
ircd/s_err.c

index 579cd21831c4a8fc8574d4212933811e9b5c5a72..fc991ab28650bb2c27bfb8956d1a6af72f6402c9 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,34 @@
+2005-10-30  Michael Poole <mdpoole@troilus.org>
+
+       * ircd/channel.c (mode_parse_apass): Move all send_reply() errors
+       inside an if (MyUser(state->sptr)) test.
+
+       * ircd/m_join.c (m_join): Reorganize zannel join check to match
+       surrounding code.
+
+2005-10-30  Carlo Wood <run@alinoe.com>
+
+       * ircd/channel.c (sub1_from_channel): Delay destruction for -A
+       channels.  They become zombie channels (zannels).
+       (mode_parse_upass): Add duration to ERR_NOMANAGER message.
+       (mode_parse_apass): Likewise.  Unconditionally set the member who
+       sets Apass as oplevel 0.  Clear Upass when clearing Apass.
+       (joinbuf_join): Remove code to pass oplevel in JOIN.
+
+       * ircd/m_burst.c (ms_burst): Handle zannels.
+
+       * ircd/m_join.c (m_join): Handle a join to a zannel.  If the user
+       is joining with ops and/or an oplevel, send those.
+       (ms_join): Stop trying to parse oplevels in JOIN.  Copy join
+       timestamp when a user joins a zannel.
+
+       * ircd/m_mode.c (ms_mode): Never generate HACK3.  Silently allow a
+       user to op himself if he is the only one in a channel.
+
+       * ircd/s_err.c (ERR_UPASSSET): Remove extra space.
+       (ERR_UPASSNOTSET): Likewise.
+       (ERR_NOMANAGER): Add field for channel lifetime.
+
 2005-10-30  Michael Poole <mdpoole@troilus.org>
 
        * ircd/m_join.c (m_join): Fix check for OVERRIDE when the real
index 0b0fb0e857ef53c04121a0f61c9be6fd6598dd59..5502bb305360de0624ffa81587e27469761a903c 100644 (file)
@@ -258,18 +258,37 @@ int sub1_from_channel(struct Channel* chptr)
 
   chptr->users = 0;
 
-  /* There is a semantics problem here: Assuming no fragments across a
-   * split, a channel without Apass could be maliciously destroyed and
-   * recreated, and someone could set apass on the new instance.
-   *
-   * This could be fixed by preserving the empty non-Apass channel for
-   * the same time as if it had an Apass (but removing +i and +l), and
-   * reopping the first user to rejoin.  However, preventing net rides
-   * requires a backwards-incompatible protocol change..
+  /*
+   * Also channels without Apass set need to be kept alive,
+   * otherwise Bad Guys(tm) would be able to takeover
+   * existing channels too easily, and then set an Apass!
+   * However, if a channel without Apass becomes empty
+   * then we try to be kind to them and remove possible
+   * limiting modes.
+   */
+  chptr->mode.mode &= ~MODE_INVITEONLY;
+  chptr->mode.limit = 0;
+  /*
+   * We do NOT reset a possible key or bans because when
+   * the 'channel owners' can't get in because of a key
+   * or ban then apparently there was a fight/takeover
+   * on the channel and we want them to contact IRC opers
+   * who then will educate them on the use of Apass/Upass.
    */
-  if (!chptr->mode.apass[0])         /* If no Apass, destroy now. */
-    destruct_channel(chptr);
-  else if (TStime() - chptr->creationtime < 172800)    /* Channel younger than 48 hours? */
+  if (!chptr->mode.apass[0])                   /* If no Apass, reset all modes. */
+  {
+    struct Ban *link, *next;
+    chptr->mode.mode = 0;
+    *chptr->mode.key = '\0';
+    while (chptr->invites)
+      del_invite(chptr->invites->value.cptr, chptr);
+    for (link = chptr->banlist; link; link = next) {
+      next = link->next;
+      free_ban(link);
+    }
+    chptr->banlist = NULL;
+  }
+  if (TStime() - chptr->creationtime < 172800) /* Channel younger than 48 hours? */
     schedule_destruct_event_1m(chptr);         /* Get rid of it in approximately 4-5 minutes */
   else
     schedule_destruct_event_48h(chptr);                /* Get rid of it in approximately 48 hours */
@@ -2388,7 +2407,9 @@ mode_parse_upass(struct ParseState *state, int *flag_p)
       send_reply(state->sptr, ERR_NOTMANAGER, state->chptr->chname,
                  state->chptr->chname);
     } else {
-      send_reply(state->sptr, ERR_NOMANAGER, state->chptr->chname);
+      send_reply(state->sptr, ERR_NOMANAGER, state->chptr->chname,
+          (TStime() - state->chptr->creationtime < 172800) ?
+         "approximately 4-5 minutes" : "approximately 48 hours");
     }
     return;
   }
@@ -2480,31 +2501,48 @@ mode_parse_apass(struct ParseState *state, int *flag_p)
     return;
   }
 
-  /* If a non-service user is trying to force it, refuse. */
-  if (state->flags & MODE_PARSE_FORCE && MyUser(state->sptr)
-      && !HasPriv(state->sptr, PRIV_APASS_OPMODE)) {
-    send_reply(state->sptr, ERR_NOTMANAGER, state->chptr->chname,
-               state->chptr->chname);
-    return;
-  }
-
-  /* Don't allow to change the Apass if the channel is older than 48 hours. */
-  if (MyUser(state->sptr)
-      && TStime() - state->chptr->creationtime >= 172800
-      && !IsAnOper(state->sptr)) {
-    send_reply(state->sptr, ERR_CHANSECURED, state->chptr->chname);
-    return;
-  }
-
-  /* If they are not the channel manager, they are not allowed to change it */
-  if (MyUser(state->sptr) && !(state->flags & MODE_PARSE_FORCE || IsChannelManager(state->member))) {
-    if (*state->chptr->mode.apass) {
-      send_reply(state->sptr, ERR_NOTMANAGER, state->chptr->chname,
-                 state->chptr->chname);
+  if (MyUser(state->sptr)) {
+    if (state->flags & MODE_PARSE_FORCE) {
+      /* If an unprivileged oper is trying to force it, refuse. */
+      if (!HasPriv(state->sptr, PRIV_APASS_OPMODE)) {
+        send_reply(state->sptr, ERR_NOTMANAGER, state->chptr->chname,
+                   state->chptr->chname);
+        return;
+      }
     } else {
-      send_reply(state->sptr, ERR_NOMANAGER, state->chptr->chname);
+      /* If they are not the channel manager, they are not allowed to change it. */
+      if (!IsChannelManager(state->member)) {
+        if (*state->chptr->mode.apass) {
+          send_reply(state->sptr, ERR_NOTMANAGER, state->chptr->chname,
+                     state->chptr->chname);
+        } else {
+          send_reply(state->sptr, ERR_NOMANAGER, state->chptr->chname,
+                     (TStime() - state->chptr->creationtime < 172800) ?
+                     "approximately 4-5 minutes" : "approximately 48 hours");
+        }
+        return;
+      }
+      /* Can't remove the Apass while Upass is still set. */
+      if (state->dir == MODE_DEL && *state->chptr->mode.upass) {
+        send_reply(state->sptr, ERR_UPASSSET, state->chptr->chname, state->chptr->chname);
+        return;
+      }
+      /* Can't add an Apass if one is set, nor can one remove the wrong Apass. */
+      if ((state->dir == MODE_ADD && *state->chptr->mode.apass) ||
+          (state->dir == MODE_DEL && ircd_strcmp(state->chptr->mode.apass, t_str))) {
+        send_reply(state->sptr, ERR_KEYSET, state->chptr->chname);
+        return;
+      }
+    }
+
+    /* Forbid removing the Apass if the channel is older than 48 hours
+     * unless an oper is doing it. */
+    if (TStime() - state->chptr->creationtime >= 172800
+        && state->dir == MODE_DEL
+        && !IsAnOper(state->sptr)) {
+      send_reply(state->sptr, ERR_CHANSECURED, state->chptr->chname);
+      return;
     }
-    return;
   }
 
   if (state->done & DONE_APASS) /* allow apass to be set only once */
@@ -2523,20 +2561,6 @@ mode_parse_apass(struct ParseState *state, int *flag_p)
   if (!state->mbuf)
     return;
 
-  if (!(state->flags & MODE_PARSE_FORCE)) {
-    /* can't remove the apass while upass is still set */
-    if (state->dir == MODE_DEL && *state->chptr->mode.upass) {
-      send_reply(state->sptr, ERR_UPASSSET, state->chptr->chname, state->chptr->chname);
-      return;
-    }
-    /* can't add an apass if one is set, nor can one remove the wrong apass */
-    if ((state->dir == MODE_ADD && *state->chptr->mode.apass) ||
-       (state->dir == MODE_DEL && ircd_strcmp(state->chptr->mode.apass, t_str))) {
-      send_reply(state->sptr, ERR_KEYSET, state->chptr->chname);
-      return;
-    }
-  }
-
   if (!(state->flags & MODE_PARSE_WIPEOUT) && state->dir == MODE_ADD &&
       !ircd_strcmp(state->chptr->mode.apass, t_str))
     return; /* no apass change */
@@ -2565,11 +2589,20 @@ mode_parse_apass(struct ParseState *state, int *flag_p)
        send_reply(state->sptr, RPL_APASSWARN_SECRET, state->chptr->chname,
                    state->chptr->mode.apass);
       }
-      /* Give the channel manager level 0 ops. */
-      if (!(state->flags & MODE_PARSE_FORCE) && IsChannelManager(state->member))
+      /* Give the channel manager level 0 ops.
+         There should not be tested for IsChannelManager here because
+        on the local server it is impossible to set the apass if one
+        isn't a channel manager and remote servers might need to sync
+        the oplevel here: when someone creates a channel (and becomes
+        channel manager) during a net.break, and only sets the Apass
+        after the net rejoined, they will have oplevel MAXOPLEVEL on
+        all remote servers. */
+      if (state->member)
         SetOpLevel(state->member, 0);
     } else { /* remove the old apass */
       *state->chptr->mode.apass = '\0';
+      /* Clear Upass so that there is never a Upass set when a zannel is burst. */
+      *state->chptr->mode.upass = '\0';
       if (MyUser(state->sptr))
         send_reply(state->sptr, RPL_APASSWARN_CLEAR);
       /* Revert everyone to MAXOPLEVEL. */
@@ -3336,17 +3369,10 @@ joinbuf_join(struct JoinBuf *jbuf, struct Channel *chan, unsigned int flags)
     else
       add_user_to_channel(chan, jbuf->jb_source, flags, oplevel);
 
-    /* send notification to all servers */
+    /* send JOIN notification to all servers (CREATE is sent later). */
     if (jbuf->jb_type != JOINBUF_TYPE_CREATE && !is_local)
-    {
-      if (flags & CHFL_CHANOP) {
-        assert(oplevel == 0 || oplevel == 1);
-        sendcmdto_serv_butone(jbuf->jb_source, CMD_JOIN, jbuf->jb_connect,
-                              "%u:%H %Tu", oplevel, chan, chan->creationtime);
-      } else
-        sendcmdto_serv_butone(jbuf->jb_source, CMD_JOIN, jbuf->jb_connect,
-                              "%H %Tu", chan, chan->creationtime);
-    }
+      sendcmdto_serv_butone(jbuf->jb_source, CMD_JOIN, jbuf->jb_connect,
+                           "%H %Tu", chan, chan->creationtime);
 
     if (!((chan->mode.mode & MODE_DELJOINS) && !(flags & CHFL_VOICED_OR_OPPED))) {
       /* Send the notification to the channel */
index 1746fda68601edae3755f6ff43e5309c4eabc455..f82de0e51b36d167e5011849dacc5a04c807f60a 100644 (file)
@@ -219,6 +219,57 @@ int ms_burst(struct Client *cptr, struct Client *sptr, int parc, char *parv[])
 
   timestamp = atoi(parv[2]);
 
+  if (chptr->creationtime)     /* 0 for new (empty) channels,
+                                   i.e. when this server just restarted. */
+  {
+    if (parc == 3)             /* Zannel BURST? */
+    {
+      /* An empty channel without +A set, will cause a BURST message
+        with exactly 3 parameters (because all modes have been reset).
+        If the timestamp on such channels is only a few seconds older
+        from our own, then we ignore this burst: we do not deop our
+        own side.
+        Likewise, we expect the other (empty) side to copy our timestamp
+        from our own BURST message, even though it is slightly larger.
+
+        The reason for this is to allow people to join an empty
+        non-A channel (a zannel) during a net.split, and not be
+        deopped when the net reconnects (with another zannel). When
+        someone joins a split zannel, their side increments the TS by one.
+        If they cycle a few times then we still don't have a reason to
+        deop them. Theoretically I see no reason not to accept ANY timestamp,
+        but to be sure, we only accept timestamps that are just a few
+        seconds off (one second for each time they cycled the channel). */
+
+      /* Don't even deop users who cycled four times during the net.break. */
+      if (timestamp < chptr->creationtime &&
+          chptr->creationtime <= timestamp + 4 &&
+         chptr->users != 0)    /* Only do this when WE have users, so that
+                                  if we do this the BURST that we sent has
+                                  parc > 3 and the other side will use the
+                                  test below: */
+       timestamp = chptr->creationtime; /* Do not deop our side. */
+    }
+    else if (chptr->creationtime < timestamp &&
+             timestamp <= chptr->creationtime + 4 &&
+            chptr->users == 0)
+    {
+      /* If one side of the net.junction does the above
+         timestamp = chptr->creationtime, then the other
+        side must do this: */
+      chptr->creationtime = timestamp; /* Use the same TS on both sides. */
+    }
+    /* In more complex cases, we might still end up with a
+       creationtime desync of a few seconds, but that should
+       be synced automatically rather quickly (every JOIN
+       caries a timestamp and will sync it; modes by users do
+       not carry timestamps and are accepted regardless).
+       Only when nobody joins the channel on the side with
+       the oldest timestamp before a new net.break occurs
+       precisely inbetween the desync, an unexpected bounce
+       might happen on reconnect. */
+  }
+
   if (!chptr->creationtime || chptr->creationtime > timestamp) {
     /*
      * Kick local members if channel is +i or +k and our TS was larger
index d274e35db13d6c5f2a2656f019bf850c8658ceff..9056c077837cb09dde7a6d3d4e964c3976b35320 100644 (file)
@@ -182,15 +182,17 @@ int m_join(struct Client *cptr, struct Client *sptr, int parc, char *parv[])
       int flags = CHFL_DEOPPED;
       int err = 0;
 
-      /* Check target change limits. */
-
       /* Check Apass/Upass -- since we only ever look at a single
        * "key" per channel now, this hampers brute force attacks. */
       if (key && !strcmp(key, chptr->mode.apass))
         flags = CHFL_CHANOP | CHFL_CHANNEL_MANAGER;
       else if (key && !strcmp(key, chptr->mode.upass))
         flags = CHFL_CHANOP;
-      else if (IsInvited(sptr, chptr)) {
+      else if (chptr->users == 0 && !chptr->mode.apass[0]) {
+        /* Joining a zombie channel (zannel): give ops and increment TS. */
+        flags = CHFL_CHANOP;
+        chptr->creationtime++;
+      } else if (IsInvited(sptr, chptr)) {
         /* Invites bypass these other checks. */
       } else if (chptr->mode.mode & MODE_INVITEONLY)
         err = ERR_INVITEONLYCHAN;
@@ -217,7 +219,7 @@ int m_join(struct Client *cptr, struct Client *sptr, int parc, char *parv[])
           if (strcmp(chptr->mode.key, "OVERRIDE")
               && strcmp(chptr->mode.apass, "OVERRIDE")
               && strcmp(chptr->mode.upass, "OVERRIDE")) {
-            send_reply(sptr, err, chptr->chname);
+            send_reply(sptr, ERR_DONTCHEAT, chptr->chname);
             continue;
           }
           break;
@@ -242,6 +244,15 @@ int m_join(struct Client *cptr, struct Client *sptr, int parc, char *parv[])
       }
 
       joinbuf_join(&join, chptr, flags);
+      if (flags & CHFL_CHANOP) {
+        /* Send a MODE to the other servers. If the user used the A/U pass,
+        * let his server op him, otherwise let him op himself. */
+        struct ModeBuf mbuf;
+       modebuf_init(&mbuf, chptr->mode.apass[0] ? &me : sptr, cptr, chptr, MODEBUF_DEST_SERVER);
+       modebuf_mode_client(&mbuf, MODE_ADD | MODE_CHANOP, sptr,
+                            chptr->mode.apass[0] ? ((flags & CHFL_CHANNEL_MANAGER) ? 0 : 1) : MAXOPLEVEL);
+       modebuf_flush(&mbuf);
+      }
     }
 
     del_invite(sptr, chptr);
@@ -302,18 +313,7 @@ int ms_join(struct Client *cptr, struct Client *sptr, int parc, char *parv[])
   for (name = ircd_strtok(&p, chanlist, ","); name;
        name = ircd_strtok(&p, 0, ",")) {
 
-    if (name[0] == '0' && name[1] == ':')
-    {
-      flags = CHFL_CHANOP | CHFL_CHANNEL_MANAGER;
-      name += 2;
-    }
-    else if (name[0] == '1' && name[1] == ':')
-    {
-      flags = CHFL_CHANOP;
-      name += 2;
-    }
-    else
-      flags = CHFL_DEOPPED;
+    flags = CHFL_DEOPPED;
 
     if (IsLocalChannel(name) || !IsChannelName(name))
     {
@@ -349,8 +349,14 @@ int ms_join(struct Client *cptr, struct Client *sptr, int parc, char *parv[])
       else
         flags |= HasFlag(sptr, FLAG_TS8) ? CHFL_SERVOPOK : 0;
       /* Always copy the timestamp when it is older, that is the only way to
-         ensure network-wide synchronization of creation times. */
-      if (creation && creation < chptr->creationtime)
+         ensure network-wide synchronization of creation times.
+         We now also copy a creation time that only 1 second younger...
+         this is needed because the timestamp must be incremented
+         by one when someone joins an existing, but empty, channel.
+         However, this is only necessary when the channel is still
+         empty (also here) and when this channel doesn't have +A set.
+      */
+      if (creation && creation - ((!chptr->mode.apass[0] && chptr->users == 0) ? 1 : 0) <= chptr->creationtime)
        chptr->creationtime = creation;
     }
 
index 480799be6f2c5e088fd67814831cf4834dea335f..051fc630e7d606fbeeb2f14498374d9ee1ad8664 100644 (file)
@@ -179,12 +179,11 @@ ms_mode(struct Client *cptr, struct Client *sptr, int parc, char *parv[])
                    MODEBUF_DEST_HACK4));  /* Send a HACK(4) message */
     else
       /* Servers need to be able to op people who join using the Apass
-       * or upass, therefore we accept modes for channels with an Apass
-       * without generating a HACK3. */
+       * or upass, as well as people joining a zannel, therefore we no
+       * longer generate HACK3. */
       modebuf_init(&mbuf, sptr, cptr, chptr,
                   (MODEBUF_DEST_CHANNEL | /* Send mode to clients */
-                   MODEBUF_DEST_SERVER |   /* Send mode to servers */
-                   (*chptr->mode.apass ? 0 : MODEBUF_DEST_HACK3)));
+                   MODEBUF_DEST_SERVER));   /* Send mode to servers */
 
     mode_parse(&mbuf, cptr, sptr, chptr, parc - 2, parv + 2,
               (MODE_PARSE_SET    | /* Set the mode */
@@ -192,7 +191,9 @@ ms_mode(struct Client *cptr, struct Client *sptr, int parc, char *parv[])
                MODE_PARSE_FORCE),  /* And force it to be accepted */
                NULL);
   } else {
-    if (!(member = find_member_link(chptr, sptr)) || !IsChanOp(member)) {
+    if (!(member = find_member_link(chptr, sptr))
+        /* Allow people to op themselves on an empty channel. */
+        || (!IsChanOp(member) && chptr->users > 1)) {
       modebuf_init(&mbuf, sptr, cptr, chptr,
                   (MODEBUF_DEST_SERVER |  /* Send mode to server */
                    MODEBUF_DEST_HACK2  |  /* Send a HACK(2) message */
index ed2e509762431405f22d2573e660a5f1429f2626..77f132a5ac30fad551dfc3ae4ec00fe8034af6c9 100644 (file)
@@ -1158,13 +1158,13 @@ static Numeric replyTable[] = {
 /* 562 */
   { ERR_CHANSECURED, "%s :Channel is older than 48 hours and secured. Cannot change Admin pass anymore", "562" },
 /* 563 */
-  { ERR_UPASSSET, "%s :Cannot remove Admin pass (+A) while User pass (+U) is still set.  First use /MODE %s -U <userpass>", "563" },
+  { ERR_UPASSSET, "%s :Cannot remove Admin pass (+A) while User pass (+U) is still set. First use /MODE %s -U <userpass>", "563" },
 /* 564 */
-  { ERR_UPASSNOTSET, "%s :Cannot set user pass (+U) until Admin pass (+A) is set.  First use /MODE %s +A <adminpass>", "564" },
+  { ERR_UPASSNOTSET, "%s :Cannot set user pass (+U) until Admin pass (+A) is set. First use /MODE %s +A <adminpass>", "564" },
 /* 565 */
   { 0 },
 /* 566 */
-  { ERR_NOMANAGER, "%s :Re-create the channel.  The channel must be completely empty before it can be recreated.", "566" },
+  { ERR_NOMANAGER, "%s :Re-create the channel. The channel must be completely empty for a period of %s before it can be recreated.", "566" },
 /* 567 */
   { ERR_UPASS_SAME_APASS, "%s :Cannot use the same pass for both admin (+A) and user (+U) pass.", "567" },
 /* 568 */