Re-check protection in the enforce sweep and key the kicker cache by registration
All checks were successful
CI / check (push) Successful in 5m19s

This commit is contained in:
Jean Chevronnet 2026-07-19 19:19:20 +00:00
parent 118782a4bf
commit 071380efae
No known key found for this signature in database
2 changed files with 18 additions and 11 deletions

View file

@ -30,12 +30,13 @@ impl Engine {
// list's revision changes). // list's revision changes).
let mut reason: Option<&'static str> = k.violation(text); let mut reason: Option<&'static str> = k.violation(text);
if reason.is_none() && k.badwords && !c.badwords.is_empty() { if reason.is_none() && k.badwords && !c.badwords.is_empty() {
let rev = c.badwords_rev; let (rev, ts) = (c.badwords_rev, c.ts);
if self.badword_cache.get(channel).map(|cb| cb.rev) != Some(rev) { let key = channel.to_ascii_lowercase();
if self.badword_cache.get(&key).map(|cb| (cb.rev, cb.ts)) != Some((rev, ts)) {
let set = db::build_badword_set(&c.badwords); let set = db::build_badword_set(&c.badwords);
self.badword_cache.insert(channel.to_string(), CachedBadwords { rev, set }); self.badword_cache.insert(key.clone(), CachedBadwords { rev, ts, set });
} }
if self.badword_cache.get(channel).unwrap().set.is_match(text) { if self.badword_cache.get(&key).unwrap().set.is_match(text) {
reason = Some("Watch your language!"); reason = Some("Watch your language!");
} }
} }
@ -127,8 +128,9 @@ impl Engine {
} }
let bot = c.assigned_bot.as_deref()?; let bot = c.assigned_bot.as_deref()?;
let botuid = self.network.uid_by_nick(bot)?.to_string(); let botuid = self.network.uid_by_nick(bot)?.to_string();
let rev = c.triggers_rev; let (rev, ts) = (c.triggers_rev, c.ts);
if self.trigger_cache.get(channel).map(|ct| ct.rev) != Some(rev) { let key = channel.to_ascii_lowercase();
if self.trigger_cache.get(&key).map(|ct| (ct.rev, ct.ts)) != Some((rev, ts)) {
let entries = c let entries = c
.triggers .triggers
.iter() .iter()
@ -141,14 +143,14 @@ impl Engine {
.map(|re| TriggerRt { re, response: t.response.clone(), cooldown: t.cooldown, last_fired: 0 }) .map(|re| TriggerRt { re, response: t.response.clone(), cooldown: t.cooldown, last_fired: 0 })
}) })
.collect(); .collect();
self.trigger_cache.insert(channel.to_string(), CachedTriggers { rev, entries }); self.trigger_cache.insert(key.clone(), CachedTriggers { rev, ts, entries });
} }
let now = self.now_secs(); let now = self.now_secs();
let nick = self.network.nick_of(from).unwrap_or(from).to_string(); let nick = self.network.nick_of(from).unwrap_or(from).to_string();
// First matching trigger wins; $1..$9 fill from capture groups, $nick from // First matching trigger wins; $1..$9 fill from capture groups, $nick from
// the speaker. A trigger still cooling down suppresses the response. // the speaker. A trigger still cooling down suppresses the response.
let cached = self.trigger_cache.get_mut(channel).unwrap(); let cached = self.trigger_cache.get_mut(&key).unwrap();
let mut response = None; let mut response = None;
for e in cached.entries.iter_mut() { for e in cached.entries.iter_mut() {
if let Some(caps) = e.re.captures(text) { if let Some(caps) = e.re.captures(text) {

View file

@ -171,11 +171,13 @@ const ENFORCE_GRACE: u64 = 60;
struct CachedBadwords { struct CachedBadwords {
rev: u64, rev: u64,
ts: u64, // channel registration ts, so a drop+re-register (rev resets to 0) can't alias
set: regex::RegexSet, set: regex::RegexSet,
} }
struct CachedTriggers { struct CachedTriggers {
rev: u64, rev: u64,
ts: u64, // see CachedBadwords.ts
entries: Vec<TriggerRt>, entries: Vec<TriggerRt>,
} }
@ -996,7 +998,11 @@ impl Engine {
let p = self.pending_enforce.remove(i); let p = self.pending_enforce.remove(i);
let still_there = self.network.uid_by_nick(&p.nick) == Some(p.uid.as_str()); let still_there = self.network.uid_by_nick(&p.nick) == Some(p.uid.as_str());
let identified = self.network.account_of(&p.uid) == self.db.resolve_account(&p.nick); let identified = self.network.account_of(&p.uid) == self.db.resolve_account(&p.nick);
if still_there && !identified { // Re-check protection at fire time: the owner may have turned KILL
// off during the grace window, in which case don't rename.
let account = self.db.resolve_account(&p.nick).map(str::to_string);
let wants = account.as_deref().is_some_and(|a| self.db.account_wants_protect(a));
if still_there && !identified && wants {
fire.push(p.uid); fire.push(p.uid);
} }
} else { } else {
@ -1004,8 +1010,7 @@ impl Engine {
} }
} }
for uid in fire { for uid in fire {
let guest = format!("{}{}", self.guest_nick, self.enforce_seq); let guest = echo_api::next_guest_nick(&self.guest_nick, &mut self.enforce_seq, &self.network, &self.db);
self.enforce_seq = self.enforce_seq.wrapping_add(1);
if let Some(ns) = self.nick_service.clone() { if let Some(ns) = self.nick_service.clone() {
self.emit_irc(NetAction::Notice { from: ns, to: uid.clone(), text: "You didn't identify in time; you've been renamed.".to_string() }); self.emit_irc(NetAction::Notice { from: ns, to: uid.clone(), text: "You didn't identify in time; you've been renamed.".to_string() });
} }