engine: reintroduce a killed service agent
All checks were successful
CI / check (push) Successful in 3m46s

A KILL of a services pseudo-client only reintroduced BotServ bots; a killed
core agent (NickServ, ChanServ, …) fell through to the forget-user path and
stayed off the network until a services restart. UserKilled now recognises an
agent by its fixed uid and re-emits its introduction, so an oper can't KILL
services down.
This commit is contained in:
Jean Chevronnet 2026-07-16 10:16:48 +00:00
parent 4aff734340
commit e5e04b4130
No known key found for this signature in database
2 changed files with 21 additions and 0 deletions

View file

@ -1044,6 +1044,17 @@ impl Engine {
Vec::new()
}
NetEvent::UserKilled { uid } => {
// A killed service agent (NickServ, ChanServ, …) has a fixed uid;
// bring it straight back so an oper can't KILL it off the network.
if let Some(intro) = self.services.iter().find(|s| s.uid() == uid).map(|s| NetAction::IntroduceUser {
uid: s.uid().to_string(),
nick: s.nick().to_string(),
ident: "services".to_string(),
host: s.host().to_string(),
gecos: s.gecos().to_string(),
}) {
return vec![intro];
}
// If the ircd killed one of our bots, forget it so reconcile
// reintroduces it (and rejoins its channels); a killed real user is
// simply gone.

View file

@ -1923,6 +1923,16 @@
assert!(out.iter().any(|a| matches!(a, NetAction::ServiceJoin { channel, .. } if channel == "#c")), "reintroduced bot rejoins #c: {out:?}");
}
// Killing a service agent (NickServ etc.) reintroduces it — an oper can't
// take services off the network with a KILL.
#[test]
fn killed_service_agent_is_reintroduced() {
let mut e = engine_with("killagent", "alice", "sesame");
// engine_with wires NickServ at uid 42SAAAAAA.
let out = e.handle(NetEvent::UserKilled { uid: "42SAAAAAA".into() });
assert!(out.iter().any(|a| matches!(a, NetAction::IntroduceUser { uid, nick, .. } if uid == "42SAAAAAA" && !nick.is_empty())), "killed agent reintroduced: {out:?}");
}
// A channel that expires with an assigned bot parts the bot in the same
// sweep — it must not linger in a channel it no longer serves.
#[test]