Add ChanServ mode lock (MLOCK)

Founders lock channel modes with MLOCK <#channel> [modes]. The lock is a
ChannelMlock event, so it replicates and compacts like everything else;
it is applied on channel creation alongside +r and enforced by reverting
any FMODE that breaks it. Our own changes are filtered so enforcement
can't loop. Simple (paramless) modes for now.
This commit is contained in:
Jean Chevronnet 2026-07-12 09:33:23 +00:00
parent 727713ee3f
commit 353aee1b54
No known key found for this signature in database
5 changed files with 248 additions and 14 deletions

View file

@ -95,6 +95,17 @@ impl Protocol for InspIrcd {
Some(chan) if !chan.is_empty() => vec![NetEvent::ChannelCreate { channel: chan.to_string() }],
_ => vec![],
},
// :<src> FMODE <chan> <ts> <modes> [params] — a channel mode change.
// Skip changes we made ourselves so enforcement can't loop.
"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('#') => {
vec![NetEvent::ChannelModeChange { channel: chan.to_string(), modes: modes.to_string() }]
}
_ => vec![],
}
}
"QUIT" => vec![NetEvent::Quit { uid: source.unwrap_or_default() }],
// account-registration relay from an ircd:
// ACCTREGISTER <reqid> <origin> <kind> <account> <p2> :<p3>
@ -247,4 +258,16 @@ mod tests {
"{ev:?}"
);
}
// A peer FMODE surfaces as a mode change; our own (sid 42S) is filtered out.
#[test]
fn parses_fmode_and_filters_own() {
let ev = proto().parse(":0IRAAAAAB FMODE #chan 1783845132 +m");
assert!(
matches!(ev.as_slice(), [NetEvent::ChannelModeChange { channel, modes }] if channel == "#chan" && modes == "+m"),
"{ev:?}"
);
let ev = proto().parse(":42S FMODE #chan 1783845132 -m");
assert!(!ev.iter().any(|e| matches!(e, NetEvent::ChannelModeChange { .. })), "own change filtered: {ev:?}");
}
}

View file

@ -15,6 +15,9 @@ pub enum NetEvent {
// A channel was created or bursted (InspIRCd FJOIN). Subsequent single joins
// arrive as IJOIN and are not surfaced.
ChannelCreate { channel: String },
// A channel's modes changed (FMODE), for enforcing mode locks. Our own
// changes are filtered out by the protocol layer.
ChannelModeChange { channel: String, modes: String },
Quit { uid: String },
// An ircd relaying an IRCv3 account-registration request to us as the authority.
AccountRequest { reqid: String, origin: String, kind: String, account: String, p2: String, p3: String },