+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
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 */
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;
}
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 */
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 */
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. */
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 */
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
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;
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;
}
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);
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))
{
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;
}
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 */
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 */
/* 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 */