nickserv/chanserv: announce a blocked look-alike registration to the staff feed (ServiceCtx alert drained by dispatch)
All checks were successful
CI / check (push) Successful in 3m44s

This commit is contained in:
Jean Chevronnet 2026-07-19 00:24:27 +00:00
parent 89ac01d0b0
commit 0d16240606
No known key found for this signature in database
5 changed files with 46 additions and 0 deletions

View file

@ -96,11 +96,19 @@ impl Engine {
for key in std::mem::take(&mut ctx.stats) {
self.bump(&key);
}
// Staff-feed alerts a command raised (e.g. a blocked look-alike name),
// prefixed with who the sender is, routed to the log channel.
let alerts = std::mem::take(&mut ctx.alerts);
// A command may have changed the bot registry (BotServ BOT ADD/DEL).
let mut out = ctx.actions;
out.extend(self.reconcile_bots());
// Announce whatever this command changed to the staff audit channel.
out.extend(self.audit_feed(audit_mark, &nick, account.as_deref()));
for (cat, text) in alerts {
if let Some(line) = self.feed(&cat, format!("{} {text}", self.who(from))) {
out.push(line);
}
}
self.apply_dict_limit(&mut out);
self.apply_msg_style(&mut out);
out

View file

@ -5528,6 +5528,32 @@ fn fantasy_dict_emits_a_lookup_via_the_bot() {
);
}
// A blocked look-alike channel registration refuses the user AND tells the staff
// feed who tried it.
#[test]
fn confusable_register_alerts_the_staff_feed() {
use echo_chanserv::ChanServ;
let mut db = svc_db("confalert");
db.register("boss", "sesame", None).unwrap();
let mut e = Engine::new(vec![ns(), Box::new(ChanServ { uid: "42SAAAAAB".into() })], db);
e.set_sid("42S".into());
e.set_log_channel(Some("#services".into()));
e.set_debugserv_uid("42SAAAAAO");
e.handle(NetEvent::UserConnect { uid: "000AAAAAB".into(), nick: "boss".into(), host: "host.fr".into(), ip: "1.2.3.4".into() });
e.handle(NetEvent::Privmsg { from: "000AAAAAB".into(), to: "42SAAAAAA".into(), text: "IDENTIFY sesame".into() });
let chan = "#teѕt"; // Latin te + Cyrillic ѕ + Latin t — a look-alike
e.handle(NetEvent::Join { uid: "000AAAAAB".into(), channel: chan.into(), op: true });
let out = e.handle(NetEvent::Privmsg { from: "000AAAAAB".into(), to: "42SAAAAAB".into(), text: format!("REGISTER {chan}") });
// The user is refused,
assert!(out.iter().any(|a| matches!(a, NetAction::Notice { to, text, .. } if to == "000AAAAAB" && text.contains("alphabets"))), "user refused: {out:?}");
// and #services is told who tried it.
assert!(
out.iter().any(|a| matches!(a, NetAction::Privmsg { from, to, text }
if from == "42SAAAAAO" && to == "#services" && text.contains("[REGISTER]") && text.contains("boss") && text.contains("look-alike"))),
"staff feed alert: {out:?}"
);
}
// Perf harness — not part of the normal suite. Run:
// cargo test --release -p echo -- --ignored --nocapture bench_engine
// Measures the two costs that actually bound the single-threaded engine: the