diff --git a/src/engine/mod.rs b/src/engine/mod.rs index e374746..089eed3 100644 --- a/src/engine/mod.rs +++ b/src/engine/mod.rs @@ -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. diff --git a/src/engine/tests.rs b/src/engine/tests.rs index 7e66e54..dcec81e 100644 --- a/src/engine/tests.rs +++ b/src/engine/tests.rs @@ -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]