OperServ: SNLINE realname bans

SNLINE ADD [+expiry] <realname-regex> <reason> / DEL / LIST — a realname
R-line the ircd matches against connecting users' realnames. It's just
another kind ("R") in the generalized X-line handler: same event-sourced
list, lazy expiry, and burst re-assertion as AKILL/SQLINE, keyed by
(kind, mask). The mask is kept verbatim (a single-token regex; use . or
\\s for spaces) and an all-wildcard pattern is refused. STATS now counts
it too.
This commit is contained in:
Jean Chevronnet 2026-07-14 01:36:08 +00:00
parent a67409e0d3
commit 78ad909706
No known key found for this signature in database
4 changed files with 17 additions and 2 deletions

View file

@ -119,6 +119,10 @@ pub const AKILL: Xline = Xline { kind: "G", name: "AKILL", target: "user@host",
// SQLINE: a nick Q-line. The mask is a nick glob (no '@'); wildcards allowed.
pub const SQLINE: Xline = Xline { kind: "Q", name: "SQLINE", target: "nick", normalize: norm_nick };
// SNLINE: a realname R-line. The mask is a regex the ircd matches against a
// connecting user's realname (use `.` for spaces, e.g. `.*free.money.*`).
pub const SNLINE: Xline = Xline { kind: "R", name: "SNLINE", target: "realname-regex", normalize: norm_realname };
fn norm_userhost(input: &str) -> Option<String> {
let body = input.rsplit('!').next().unwrap_or(input);
let (user, host) = body.split_once('@')?;
@ -137,6 +141,12 @@ fn norm_nick(input: &str) -> Option<String> {
Some(input.to_ascii_lowercase())
}
fn norm_realname(input: &str) -> Option<String> {
// A realname regex the ircd evaluates: keep it verbatim (case matters), just
// require it non-empty. A single token — use `.`/`\s` for spaces.
(!input.is_empty()).then(|| input.to_string())
}
// A mask whose every meaningful character is a wildcard would match nearly all.
fn too_wide(mask: &str) -> bool {
let trivial = |s: &str| s.chars().all(|c| c == '*' || c == '?' || c == '.' || c == '@');