usermodes: gate the oper-only flags (+W showwhois, +H hideoper, +h helpop, +s snomask) behind is_oper — a non-oper who tries to set one now gets 481 instead of the mode silently sticking or failing without a reason

This commit is contained in:
Jean Chevronnet 2026-08-14 17:04:28 +00:00
parent 40d868f4cf
commit 370b7bc57a

View file

@ -1165,7 +1165,7 @@ static HIDECHANS: UFlag = UFlag {
ch: 'I', ch: 'I',
set: set_hidechans, set: set_hidechans,
}; };
static HIDEOPER: UFlag = UFlag { static HIDEOPER: OperFlag = OperFlag {
ch: 'H', ch: 'H',
set: set_hideoper, set: set_hideoper,
}; };
@ -1181,7 +1181,7 @@ static CALLERID: UFlag = UFlag {
ch: 'g', ch: 'g',
set: set_callerid, set: set_callerid,
}; };
static SHOWWHOIS: UFlag = UFlag { static SHOWWHOIS: OperFlag = OperFlag {
ch: 'W', ch: 'W',
set: set_showwhois, 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. /// `+o` is granted only by OPER; a user may `-o` (de-oper) themselves.
struct OperMode; struct OperMode;
static OPER: OperMode = 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 /// `+h` — helpop: marks a user as available for help (shown in WHOIS). Oper-only to
/// may set it on themselves; anyone may clear it. /// set; anyone may clear it.
struct HelpMode; static HELPOP: OperFlag = OperFlag {
static HELPOP: HelpMode = HelpMode; ch: 'h',
impl UserMode for HelpMode { set: set_helpop,
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
}
}
}
/// `+x` — host cloaking. The cloak string is computed once at connect by /// `+x` — host cloaking. The cloak string is computed once at connect by
/// [`crate::modules::cloak`]; this handler only toggles whether it's shown. /// [`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. /// `+s` — server-notice (snomask) receiver. Oper-only to set; anyone may drop it.
struct SnoMode; static SNOMASK: OperFlag = OperFlag {
static SNOMASK: SnoMode = SnoMode; ch: 's',
impl UserMode for SnoMode { set: set_snomask,
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
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {