From 636da13a8aa8f342dfdbf7268a12d1f4381fd3e0 Mon Sep 17 00:00:00 2001 From: reverse Date: Sun, 16 Aug 2026 03:10:15 +0000 Subject: [PATCH] mode: register +r as a services-only channel mode so a user MODE +r gets a clean rejection instead of 472 unknown-mode (chanserv still sets it via mode_sudo) --- src/mode.rs | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/src/mode.rs b/src/mode.rs index 0360b83..8a551d8 100644 --- a/src/mode.rs +++ b/src/mode.rs @@ -85,6 +85,7 @@ static CHAN_MODES: &[&(dyn ChanMode + Sync)] = &[ &INVEX, &KEY, &LIMIT, + ®ISTERED_CHAN, &MODERATED, &NOEXTERNAL, &TOPICLOCK, @@ -439,6 +440,44 @@ impl ChanMode for OperFlagChan { } } +// --- +r registered (services-only) ------------------------------------------ + +/// `+r` — a registered channel. Set only by services (under `mode_sudo`, e.g. a +/// ChanServ FMODE); a client that tries it gets a clean "services-only" rejection +/// instead of "unknown mode char". +struct RegisteredChan; +static REGISTERED_CHAN: RegisteredChan = RegisteredChan; +impl ChanMode for RegisteredChan { + fn letter(&self) -> char { + 'r' + } + fn wants_param(&self, _adding: bool) -> bool { + false + } + fn apply( + &self, + s: &mut Server, + chan: &str, + key: &str, + uid: Uid, + adding: bool, + _param: Option<&str>, + ) -> Applied { + if !s.mode_sudo { + s.numeric( + uid, + ERR_NOPRIVILEGES, + &format!("{chan} :Channel mode +r may only be set by services"), + ); + return Applied::No; + } + if let Some(c) = s.channels.get_mut(key) { + c.modes.registered = adding; + } + Applied::Yes(None) + } +} + // --- +k channel key --------------------------------------------------------- struct Key; @@ -1439,7 +1478,7 @@ mod tests { #[test] fn registry_covers_all_channel_modes() { - for c in "qaohvbeIklmntiszpONCTcSRMfjFLgGuBQAPJUdKXwD".chars() { + for c in "qaohvbeIklmntiszpONCTcSRMfjFLgGuBQAPJUdKXwDr".chars() { assert!(chan_mode(c).is_some(), "missing handler for +{c}"); } assert!(chan_mode('y').is_none());