Source channel mode changes from ChanServ

Channel modes (+r, mode lock, auto-op, enforcement) are sent from the
ChanServ pseudoclient instead of the services server, so users see
ChanServ as the setter. The engine locates the channel-managing service;
the FMODE filter now ignores anything under our SID prefix (server and
pseudoclients) so enforcement can't loop on our own changes.
This commit is contained in:
Jean Chevronnet 2026-07-12 10:18:07 +00:00
parent e785c9d8ac
commit f15c9998be
No known key found for this signature in database
5 changed files with 47 additions and 22 deletions

View file

@ -122,7 +122,8 @@ impl Protocol for InspIrcd {
"FMODE" => {
let a: Vec<&str> = tokens.collect();
match (source.as_deref(), a.first(), a.get(2)) {
(Some(src), Some(chan), Some(modes)) if src != self.sid && chan.starts_with('#') => {
// Our own uids (server SID + pseudoclients) share our SID prefix.
(Some(src), Some(chan), Some(modes)) if !src.starts_with(self.sid.as_str()) && chan.starts_with('#') => {
vec![NetEvent::ChannelModeChange { channel: chan.to_string(), modes: modes.to_string() }]
}
_ => vec![],
@ -218,10 +219,16 @@ impl Protocol for InspIrcd {
vec![self.from_us(format!("SVSNICK {} {} {}", uid, nick, now))]
}
// FMODE <chan> <ts> <modes>. The ircd drops an FMODE whose TS is newer
// than the channel's, so we send TS 1 to guarantee it applies to the
// existing channel. Sourced from our server, which +r requires.
NetAction::ChannelMode { channel, modes } => {
vec![self.from_us(format!("FMODE {} 1 {}", channel, modes))]
// than the channel's, so we send TS 1 to guarantee it applies. Sourced
// from the given pseudoclient (e.g. ChanServ) so users see who set it,
// or from the services server when `from` is empty.
NetAction::ChannelMode { from, channel, modes } => {
let cmd = format!("FMODE {} 1 {}", channel, modes);
if from.is_empty() {
vec![self.from_us(cmd)]
} else {
vec![format!(":{} {}", from, cmd)]
}
}
NetAction::Raw(s) => vec![s.clone()],
// Internal: the link layer handles this before serialization.

View file

@ -46,9 +46,10 @@ pub enum NetAction {
// Force a user's nick (SVSNICK), e.g. renaming to a guest nick on logout.
// The protocol stamps the new nick's timestamp.
ForceNick { uid: String, nick: String },
// Set channel modes from services, e.g. +r on a registered channel. The
// Set channel modes from services, e.g. +r on a registered channel. `from` is
// the pseudoclient uid to source it from (empty = the services server). The
// protocol stamps a timestamp the ircd will accept.
ChannelMode { channel: String, modes: String },
ChannelMode { from: String, channel: String, modes: String },
Raw(String),
// Internal only, never serialized to the wire: a registration whose password
// still needs its (expensive) key derivation. The link layer runs the