From: Michael Poole Date: Mon, 31 Oct 2005 23:17:29 +0000 (+0000) Subject: Make empty -A channels into zombies ("zannels") to avoid +A hijacks. X-Git-Url: http://git.pk910.de/?p=ircu2.10.12-pk.git;a=commitdiff_plain;h=534a8b34c9ece2d6aa4d003394b397ed48aa4ea9 Make empty -A channels into zombies ("zannels") to avoid +A hijacks. git-svn-id: file:///home/klmitch/undernet-ircu/undernet-ircu-svn/ircu2/branches/u2_10_12_branch@1537 c9e4aea6-c8fd-4c43-8297-357d70d61c8c --- diff --git a/ChangeLog b/ChangeLog index 579cd21..fc991ab 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,34 @@ +2005-10-30 Michael Poole + + * 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 + + * 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 * ircd/m_join.c (m_join): Fix check for OVERRIDE when the real diff --git a/ircd/channel.c b/ircd/channel.c index 0b0fb0e..5502bb3 100644 --- a/ircd/channel.c +++ b/ircd/channel.c @@ -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 */ diff --git a/ircd/m_burst.c b/ircd/m_burst.c index 1746fda..f82de0e 100644 --- a/ircd/m_burst.c +++ b/ircd/m_burst.c @@ -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 diff --git a/ircd/m_join.c b/ircd/m_join.c index d274e35..9056c07 100644 --- a/ircd/m_join.c +++ b/ircd/m_join.c @@ -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; } diff --git a/ircd/m_mode.c b/ircd/m_mode.c index 480799b..051fc63 100644 --- a/ircd/m_mode.c +++ b/ircd/m_mode.c @@ -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 */ diff --git a/ircd/s_err.c b/ircd/s_err.c index ed2e509..77f132a 100644 --- a/ircd/s_err.c +++ b/ircd/s_err.c @@ -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 ", "563" }, + { ERR_UPASSSET, "%s :Cannot remove Admin pass (+A) while User pass (+U) is still set. First use /MODE %s -U ", "563" }, /* 564 */ - { ERR_UPASSNOTSET, "%s :Cannot set user pass (+U) until Admin pass (+A) is set. First use /MODE %s +A ", "564" }, + { ERR_UPASSNOTSET, "%s :Cannot set user pass (+U) until Admin pass (+A) is set. First use /MODE %s +A ", "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 */