diff --git a/src/coremods/core_oper.rs b/src/coremods/core_oper.rs index 00c747b..915afc1 100644 --- a/src/coremods/core_oper.rs +++ b/src/coremods/core_oper.rs @@ -179,6 +179,16 @@ impl Command for Kill { .cloned() .unwrap_or_else(|| "Killed".to_string()); let Some(tuid) = s.find_nick(target) else { + // remote target: route the KILL toward the server that owns it + if let Some((uuid, _)) = s.find_remote(target) { + let (killer_uuid, killer) = s + .users + .get(&uid) + .map(|u| (u.uuid.clone(), u.nick.clone())) + .unwrap_or_default(); + s.route_kill(&killer_uuid, &uuid, &format!("{killer} ({reason})")); + return CmdResult::Ok; + } s.numeric( uid, ERR_NOSUCHNICK, diff --git a/src/link.rs b/src/link.rs index 9d8462e..544cbff 100644 --- a/src/link.rs +++ b/src/link.rs @@ -166,6 +166,7 @@ impl Server { "UID" if registered => self.link_uid_recv(uid, msg), "NICK" if registered => self.link_nick_recv(uid, msg), "QUIT" if registered => self.link_quit_recv(uid, msg), + "KILL" if registered => self.link_kill_recv(uid, msg), "PRIVMSG" if registered => self.link_message_recv(uid, msg, false), "NOTICE" if registered => self.link_message_recv(uid, msg, true), "JOIN" if registered => self.link_join_recv(uid, msg), @@ -1033,6 +1034,41 @@ impl Server { self.propagate(&format!(":{uuid} QUIT :{reason}"), Some(via)); } + /// `: KILL :` — a services/oper kill from a peer. A local + /// target is notified and removed (its QUIT tells the rest of the tree); a + /// remote target is routed one hop onward. + fn link_kill_recv(&mut self, via: Uid, msg: &Message) { + let Some(src) = msg.source.clone() else { + return; + }; + let (Some(target), Some(reason)) = + (msg.params.first().cloned(), msg.params.get(1).cloned()) + else { + return; + }; + match self.link_local_target(&target) { + Some(tuid) => { + let from = self + .uuid_prefix(&src) + .or_else(|| self.servers.get(&src).map(|sv| sv.name.clone())) + .unwrap_or_else(|| src.clone()); + let nick = self.users.get(&tuid).map(|u| u.nick.clone()).unwrap_or_default(); + self.send(tuid, format!(":{from} KILL {nick} :{reason}")); + self.remove_user(tuid, &format!("Killed ({reason})")); + } + None => { + self.forward_to_target(&target, msg, via); + } + } + } + + /// Route a KILL toward the server that owns a remote `target` uuid. + pub fn route_kill(&self, killer: &str, target: &str, reason: &str) { + if let Some(v) = self.link_toward(target) { + self.link_out(v, format!(":{killer} KILL {target} :{reason}")); + } + } + fn link_message_recv(&mut self, via: Uid, msg: &Message, notice: bool) { // : PRIVMSG <#chan|dstuuid> : let cmd = if notice { "NOTICE" } else { "PRIVMSG" };