rline: native regex engine (thompson nfa, no backtracking) + RLINE regex bans (registration/enforce/stats R/matchonnickchange)

This commit is contained in:
Jean Chevronnet 2026-08-10 21:48:52 +00:00
parent b76fbfdb79
commit 56d3774647
8 changed files with 786 additions and 1 deletions

View file

@ -243,7 +243,7 @@ impl Command for Stats {
s.numeric(uid, RPL_STATSOLINE, &format!("O * * {n} :oper"));
}
}
'k' | 'g' | 'z' | 'e' | 'q' | 's' | 'S' => {
'k' | 'g' | 'z' | 'e' | 'q' | 's' | 'S' | 'R' => {
let kind = match letter {
'k' => XKind::Kline,
'g' => XKind::Gline,
@ -251,6 +251,7 @@ impl Command for Stats {
'e' => XKind::Eline,
'q' => XKind::Qline,
'S' => XKind::Svshold,
'R' => XKind::Rline,
_ => XKind::Shun,
};
let rows: Vec<String> = s

View file

@ -35,6 +35,7 @@ pub fn commands() -> Vec<Box<dyn Command>> {
Box::new(Shun),
Box::new(Qline),
Box::new(Cban),
Box::new(Rline),
Box::new(Connect),
Box::new(ChgHost),
Box::new(ChgIdent),
@ -743,6 +744,56 @@ impl Command for Cban {
}
}
/// RLINE — ban users whose `nick!user@host realname` matches a regular expression.
/// `RLINE <regex> [<duration>] :<reason>` adds; `RLINE <regex>` removes.
struct Rline;
impl Command for Rline {
fn name(&self) -> &'static str {
"RLINE"
}
fn min_params(&self) -> usize {
1
}
fn handle(&self, s: &mut Server, uid: Uid, params: &[String]) -> CmdResult {
if !require_oper(s, uid) {
return CmdResult::Fail;
}
let pattern = params[0].clone();
let nick = s
.users
.get(&uid)
.map(|u| u.nick.clone())
.unwrap_or_default();
if params.len() < 2 {
let word = if s.remove_xline(XKind::Rline, &pattern) {
"removed"
} else {
"not found"
};
s.send(
uid,
format!(":{} NOTICE {nick} :R-line {word}: {pattern}", s.name),
);
return CmdResult::Ok;
}
if let Err(e) = crate::regex::Regex::new(&pattern) {
s.send(
uid,
format!(":{} NOTICE {nick} :Invalid RLINE regex: {e}", s.name),
);
return CmdResult::Fail;
}
let dur = parse_duration(&params[1]).unwrap_or(0);
let reason = params
.get(2)
.cloned()
.unwrap_or_else(|| "No reason given".to_string());
s.add_xline(XKind::Rline, &pattern, dur, &nick, &reason);
s.enforce_rline(&pattern, &reason);
CmdResult::Ok
}
}
/// NICKLOCK — force a user's nick and lock it so they can't change it.
/// `NICKLOCK <nick> <newnick>`; opers/services still can.
struct NickLock;

View file

@ -501,6 +501,24 @@ impl Command for Nick {
}
}
s.set_nick(uid, newnick);
// RLINE matchonnickchange: re-test the R-lines against the new identity
if s.conf_bool("rline_matchonnickchange", false) {
let info = s.users.get(&uid).map(|u| {
(
u.nick.clone(),
u.ident.clone(),
u.host.clone(),
u.addr.ip().to_string(),
u.realname.clone(),
)
});
if let Some((nk, id, ho, ip, rn)) = info {
if let Some(reason) = s.matched_rline(&nk, &id, &ho, &ip, &rn) {
s.send(uid, format!("ERROR :Closing link: ({reason})"));
s.remove_user(uid, &reason);
}
}
}
CmdResult::Ok
}
}