From 7528b78bf78914a5436a5ca538380a46295727aa Mon Sep 17 00:00:00 2001 From: Jean Date: Sun, 19 Jul 2026 13:17:54 +0000 Subject: [PATCH] Gate OperServ EXCEPTION at admin tier and reject match-all SNLINE regex --- modules/operserv/src/session.rs | 2 +- modules/operserv/src/xline.rs | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/modules/operserv/src/session.rs b/modules/operserv/src/session.rs index eda37c7..193976e 100644 --- a/modules/operserv/src/session.rs +++ b/modules/operserv/src/session.rs @@ -33,7 +33,7 @@ pub fn handle_session(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceC } pub fn handle_exception(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, db: &mut dyn Store) { - if !from.privs.has(Priv::Oper) { + if !from.privs.has(Priv::Admin) { ctx.notice(me, from.uid, "Access denied — EXCEPTION needs the \x02admin\x02 privilege."); return; } diff --git a/modules/operserv/src/xline.rs b/modules/operserv/src/xline.rs index 3174bf0..5d2a3bd 100644 --- a/modules/operserv/src/xline.rs +++ b/modules/operserv/src/xline.rs @@ -44,7 +44,7 @@ impl Xline { ctx.notice(me, from.uid, "Please give a reason."); return; } - if too_wide(&mask) { + if too_wide(&mask, self.kind == XlineKind::Rline) { ctx.notice(me, from.uid, "That mask is too wide — it would match almost everyone."); return; } @@ -169,9 +169,12 @@ fn norm_channel(input: &str) -> Option { // A mask whose every meaningful character is a wildcard would match nearly all. // A leading channel prefix is ignored so `#*` counts as too wide. -fn too_wide(mask: &str) -> bool { +fn too_wide(mask: &str, is_regex: bool) -> bool { let body = mask.strip_prefix('#').or_else(|| mask.strip_prefix('&')).unwrap_or(mask); - body.is_empty() || body.chars().all(|c| c == '*' || c == '?' || c == '.' || c == '@') + // A realname REGEX that's a bare quantifier over any-char (.+ / .* / .) matches + // everyone, so treat '+' as a wildcard too — otherwise `SNLINE ADD .+` bans the + // whole network (glob '*'/'?' and '.' are already caught). + body.is_empty() || body.chars().all(|c| c == '*' || c == '?' || c == '.' || c == '@' || (is_regex && c == '+')) } fn human_secs(secs: u64) -> String {