From 4583377361c9753a96b15e4b7d1bc9a42a47f7d6 Mon Sep 17 00:00:00 2001 From: reverse Date: Sat, 8 Aug 2026 19:15:29 +0000 Subject: [PATCH] route svs* over s2s: services pseudoserver can svsnick/svsjoin/svspart/svsmode/svslogin/svslogout local users --- src/link.rs | 105 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 104 insertions(+), 1 deletion(-) diff --git a/src/link.rs b/src/link.rs index 8761c20..9f3816a 100644 --- a/src/link.rs +++ b/src/link.rs @@ -27,7 +27,7 @@ use crate::channels::{Ban, Channel, Member, Topic}; use crate::message::Message; use crate::server::{now, Server}; use crate::socketengine::OutSink; -use crate::users::User; +use crate::users::{valid_nick, User}; use crate::Uid; /// A local server-link connection (one hop away). Distinct from a client `User`. @@ -161,6 +161,14 @@ impl Server { "KICK" if registered => self.link_kick_recv(uid, msg), "MODE" | "FMODE" if registered => self.link_mode_recv(uid, msg), "FJOIN" if registered => self.link_fjoin_recv(uid, msg), + // services (SVS*) enforcement + account login, driven by a linked + // services pseudoserver + "SVSNICK" if registered => self.link_svsnick(msg), + "SVSJOIN" if registered => self.link_svsjoin(msg), + "SVSPART" if registered => self.link_svspart(msg), + "SVSMODE" if registered => self.link_svsmode(msg), + "SVSLOGIN" if registered => self.link_svslogin(msg), + "SVSLOGOUT" if registered => self.link_svslogout(msg), "BURST" => { if let Some(l) = self.links.get_mut(&uid) { l.bursting = true; @@ -374,6 +382,101 @@ impl Server { } } + // --- services (SVS*) over S2S --------------------------------------------- + // A linked services pseudoserver enforces nick/join/part/mode and account + // login on our users with these. Authority is the link itself — only + // registered peers reach `on_link`. Targets are network UUIDs or nicks; we + // act only on locally-present targets (multi-hop forwarding is still TODO, + // like the SVSLOGIN/SASL note in the module header). Each reuses the same + // primitive as the local SVS* command, so behaviour and propagation match. + + /// Resolve an S2S target token (network UUID or nickname) to a local user. + fn link_local_target(&self, target: &str) -> Option { + self.uuid_local + .get(target) + .copied() + .or_else(|| self.find_nick(target)) + } + + /// `:src SVSNICK [ts]` — force a nick change. + fn link_svsnick(&mut self, msg: &Message) { + if msg.params.len() < 2 { + return; + } + let (target, newnick) = (&msg.params[0], &msg.params[1]); + let Some(tuid) = self.link_local_target(target) else { + return; + }; + if !valid_nick(newnick) + || self.find_nick(newnick).is_some() + || self.remote_nick.contains_key(&newnick.to_ascii_lowercase()) + { + return; // collision / invalid — services should pick a free nick + } + self.set_nick(tuid, newnick); + } + + /// `:src SVSJOIN ` — force a join. + fn link_svsjoin(&mut self, msg: &Message) { + if msg.params.len() < 2 { + return; + } + if let Some(tuid) = self.link_local_target(&msg.params[0]) { + self.join(tuid, &msg.params[1], None); + } + } + + /// `:src SVSPART [reason]` — force a part. + fn link_svspart(&mut self, msg: &Message) { + if msg.params.len() < 2 { + return; + } + if let Some(tuid) = self.link_local_target(&msg.params[0]) { + let reason = msg + .params + .get(2) + .cloned() + .unwrap_or_else(|| "Services forced part".to_string()); + self.force_part(tuid, &msg.params[1], &reason); + } + } + + /// `:src SVSMODE ` — set a user's modes (e.g. `+r`). Channel + /// modes travel as (F)MODE, so a `#` target is ignored here. + fn link_svsmode(&mut self, msg: &Message) { + if msg.params.len() < 2 || msg.params[0].starts_with('#') { + return; + } + if let Some(tuid) = self.link_local_target(&msg.params[0]) { + crate::coremods::core_mode::svs_set_user_modes(self, tuid, &msg.params[1]); + } + } + + /// `:src SVSLOGIN ` — log a user into (or, with `*`/`0`, out + /// of) a services account. + fn link_svslogin(&mut self, msg: &Message) { + if msg.params.len() < 2 { + return; + } + if let Some(tuid) = self.link_local_target(&msg.params[0]) { + let account = &msg.params[1]; + if account == "*" || account == "0" { + self.logout(tuid); + } else { + self.set_login(tuid, account); + } + } + } + + /// `:src SVSLOGOUT ` — log a user out of their account. + fn link_svslogout(&mut self, msg: &Message) { + if let Some(t) = msg.params.first() { + if let Some(tuid) = self.link_local_target(t) { + self.logout(tuid); + } + } + } + // --- inbound S2S records -------------------------------------------------- fn link_uid_recv(&mut self, via: Uid, msg: &Message) {