From 370b7bc57ae1e5df88f7edbf2b8562a35c3fa6d7 Mon Sep 17 00:00:00 2001 From: reverse Date: Fri, 14 Aug 2026 17:04:28 +0000 Subject: [PATCH] =?UTF-8?q?usermodes:=20gate=20the=20oper-only=20flags=20(?= =?UTF-8?q?+W=20showwhois,=20+H=20hideoper,=20+h=20helpop,=20+s=20snomask)?= =?UTF-8?q?=20behind=20is=5Foper=20=E2=80=94=20a=20non-oper=20who=20tries?= =?UTF-8?q?=20to=20set=20one=20now=20gets=20481=20instead=20of=20the=20mod?= =?UTF-8?q?e=20silently=20sticking=20or=20failing=20without=20a=20reason?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mode.rs | 89 +++++++++++++++++++++++++++++------------------------ 1 file changed, 49 insertions(+), 40 deletions(-) diff --git a/src/mode.rs b/src/mode.rs index a298931..84985da 100644 --- a/src/mode.rs +++ b/src/mode.rs @@ -1165,7 +1165,7 @@ static HIDECHANS: UFlag = UFlag { ch: 'I', set: set_hidechans, }; -static HIDEOPER: UFlag = UFlag { +static HIDEOPER: OperFlag = OperFlag { ch: 'H', set: set_hideoper, }; @@ -1181,7 +1181,7 @@ static CALLERID: UFlag = UFlag { ch: 'g', set: set_callerid, }; -static SHOWWHOIS: UFlag = UFlag { +static SHOWWHOIS: OperFlag = OperFlag { ch: 'W', set: set_showwhois, }; @@ -1204,6 +1204,43 @@ impl UserMode for UFlag { } } +fn set_helpop(f: &mut UserFlags, v: bool) { + f.helpop = v; +} +fn set_snomask(f: &mut UserFlags, v: bool) { + f.snomask = v; +} + +/// An oper-only boolean flag (+H / +W / +h / +s): only an operator may **set** it; +/// anyone may clear it. A non-oper who tries to set it is told they aren't an +/// operator (`ERR_NOPRIVILEGES`) and the flag is left off — it never silently +/// "sticks" with no effect. +struct OperFlag { + ch: char, + set: fn(&mut UserFlags, bool), +} +impl UserMode for OperFlag { + fn letter(&self) -> char { + self.ch + } + fn apply(&self, s: &mut Server, uid: Uid, adding: bool) -> bool { + if adding && !s.is_oper(uid) { + s.numeric( + uid, + ERR_NOPRIVILEGES, + ":Permission Denied- You're not an IRC operator", + ); + return false; + } + if let Some(u) = s.users.get_mut(&uid) { + (self.set)(&mut u.flags, adding); + true + } else { + false + } + } +} + /// `+o` is granted only by OPER; a user may `-o` (de-oper) themselves. struct OperMode; static OPER: OperMode = OperMode; @@ -1227,26 +1264,12 @@ impl UserMode for OperMode { } } -/// `+h` — helpop: marks a user as available for help (shown in WHOIS). Only opers -/// may set it on themselves; anyone may clear it. -struct HelpMode; -static HELPOP: HelpMode = HelpMode; -impl UserMode for HelpMode { - fn letter(&self) -> char { - 'h' - } - fn apply(&self, s: &mut Server, uid: Uid, adding: bool) -> bool { - if adding && !s.is_oper(uid) { - return false; // only opers may declare themselves a helpop - } - if let Some(u) = s.users.get_mut(&uid) { - u.flags.helpop = adding; - true - } else { - false - } - } -} +/// `+h` — helpop: marks a user as available for help (shown in WHOIS). Oper-only to +/// set; anyone may clear it. +static HELPOP: OperFlag = OperFlag { + ch: 'h', + set: set_helpop, +}; /// `+x` — host cloaking. The cloak string is computed once at connect by /// [`crate::modules::cloak`]; this handler only toggles whether it's shown. @@ -1312,24 +1335,10 @@ impl UserMode for RegisteredMode { } /// `+s` — server-notice (snomask) receiver. Oper-only to set; anyone may drop it. -struct SnoMode; -static SNOMASK: SnoMode = SnoMode; -impl UserMode for SnoMode { - fn letter(&self) -> char { - 's' - } - fn apply(&self, s: &mut Server, uid: Uid, adding: bool) -> bool { - if adding && !s.is_oper(uid) { - return false; // only operators receive server notices - } - if let Some(u) = s.users.get_mut(&uid) { - u.flags.snomask = adding; - true - } else { - false - } - } -} +static SNOMASK: OperFlag = OperFlag { + ch: 's', + set: set_snomask, +}; #[cfg(test)] mod tests {