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)

This commit is contained in:
Jean Chevronnet 2026-08-16 03:10:15 +00:00
parent b7600ee5aa
commit 636da13a8a

View file

@ -85,6 +85,7 @@ static CHAN_MODES: &[&(dyn ChanMode + Sync)] = &[
&INVEX, &INVEX,
&KEY, &KEY,
&LIMIT, &LIMIT,
&REGISTERED_CHAN,
&MODERATED, &MODERATED,
&NOEXTERNAL, &NOEXTERNAL,
&TOPICLOCK, &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 --------------------------------------------------------- // --- +k channel key ---------------------------------------------------------
struct Key; struct Key;
@ -1439,7 +1478,7 @@ mod tests {
#[test] #[test]
fn registry_covers_all_channel_modes() { 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(c).is_some(), "missing handler for +{c}");
} }
assert!(chan_mode('y').is_none()); assert!(chan_mode('y').is_none());