From f5cc12e8094501215e5bb931ccf8a63f4b11f0a1 Mon Sep 17 00:00:00 2001 From: reverse Date: Sat, 8 Aug 2026 20:19:14 +0000 Subject: [PATCH] connect: oper can dial a configured server link on demand --- src/coremods/core_oper.rs | 43 +++++++++++++++++++++++++++++++++++++++ src/ircd.rs | 8 ++++++-- src/main.rs | 9 ++++---- src/server.rs | 8 ++++++-- 4 files changed, 60 insertions(+), 8 deletions(-) diff --git a/src/coremods/core_oper.rs b/src/coremods/core_oper.rs index 2346232..2cbb3d6 100644 --- a/src/coremods/core_oper.rs +++ b/src/coremods/core_oper.rs @@ -34,6 +34,7 @@ pub fn commands() -> Vec> { Box::new(Eline), Box::new(Shun), Box::new(Qline), + Box::new(Connect), Box::new(ChgHost), Box::new(ChgIdent), Box::new(SetHost), @@ -671,6 +672,48 @@ impl Command for Qline { } } +/// CONNECT — dial a configured server link on demand. `CONNECT `. +struct Connect; +impl Command for Connect { + fn name(&self) -> &'static str { + "CONNECT" + } + fn min_params(&self) -> usize { + 1 + } + fn handle(&self, s: &mut Server, uid: Uid, params: &[String]) -> CmdResult { + if !require_oper(s, uid) { + return CmdResult::Fail; + } + let name = ¶ms[0]; + let Some(b) = s + .link_blocks + .iter() + .find(|b| b.name.eq_ignore_ascii_case(name)) + .cloned() + else { + onotice(s, uid, &format!("CONNECT: no link block named {name}")); + return CmdResult::Fail; + }; + if s.servers + .values() + .any(|sv| sv.name.eq_ignore_ascii_case(&b.name)) + { + onotice(s, uid, &format!("CONNECT: {} is already linked", b.name)); + return CmdResult::Fail; + } + let addr = format!("{}:{}", b.ip, b.port); + let (tx, counter) = (s.event_tx.clone(), s.conn_counter.clone()); + std::thread::spawn(move || crate::socketengine::connect_link(&addr, tx, counter)); + let by = oper_nick(s, uid); + s.snotice(&format!( + "{by} used CONNECT to {} ({}:{})", + b.name, b.ip, b.port + )); + CmdResult::Ok + } +} + /// Resolve a nick to a uid, sending ERR_NOSUCHNICK if it's unknown. fn oper_target(s: &mut Server, uid: Uid, nick: &str) -> Option { match s.find_nick(nick) { diff --git a/src/ircd.rs b/src/ircd.rs index 4e26ad0..d5734ae 100644 --- a/src/ircd.rs +++ b/src/ircd.rs @@ -66,9 +66,13 @@ pub struct Ircd { } impl Ircd { - pub fn new(cfg: Config, event_tx: Sender) -> Ircd { + pub fn new( + cfg: Config, + event_tx: Sender, + conn_counter: std::sync::Arc, + ) -> Ircd { Ircd { - server: Server::new(cfg, event_tx), + server: Server::new(cfg, event_tx, conn_counter), commands: command_table(), modules: crate::modules::default_modules(), } diff --git a/src/main.rs b/src/main.rs index 9a189e6..bbd7fe0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -44,10 +44,14 @@ fn main() { cfg.servername ); + // one uid counter shared by every listener (and by CONNECT) so ids stay unique + let counter = Arc::new(AtomicU64::new(1)); + let (tx, rx) = mpsc::channel(); let core_cfg = cfg.clone(); let core_tx = tx.clone(); // the core self-injects events (DNS results) - let core = thread::spawn(move || Ircd::new(core_cfg, core_tx).run(rx)); + let core_counter = counter.clone(); + let core = thread::spawn(move || Ircd::new(core_cfg, core_tx, core_counter).run(rx)); // background timer: drives ping/idle timeouts let tick_tx = tx.clone(); @@ -58,9 +62,6 @@ fn main() { } }); - // one uid counter shared by every listener so ids stay unique - let counter = Arc::new(AtomicU64::new(1)); - // optional TLS listener (bind_tls + tls_cert + tls_key). A cert/bind problem // disables TLS but never takes the plaintext listener down. if let (Some(bind_tls), Some(cert), Some(key)) = (&cfg.bind_tls, &cfg.tls_cert, &cfg.tls_key) { diff --git a/src/server.rs b/src/server.rs index dabd7ed..572f4e4 100644 --- a/src/server.rs +++ b/src/server.rs @@ -8,7 +8,9 @@ use std::cell::RefCell; use std::collections::{HashMap, HashSet, VecDeque}; use std::net::{SocketAddr, TcpStream}; +use std::sync::atomic::AtomicU64; use std::sync::mpsc::Sender; +use std::sync::Arc; use std::thread; use std::time::{SystemTime, UNIX_EPOCH}; @@ -148,10 +150,11 @@ pub struct Server { pub label_capture: RefCell)>>, pub history: HashMap>, // channel key -> recent messages (CHATHISTORY) pub event_tx: Sender, // self-inject events (DNS results) + pub conn_counter: Arc, // mints connection uids (for CONNECT dials) } impl Server { - pub fn new(cfg: Config, event_tx: Sender) -> Server { + pub fn new(cfg: Config, event_tx: Sender, conn_counter: Arc) -> Server { Server { name: cfg.servername, network: cfg.network, @@ -191,6 +194,7 @@ impl Server { label_capture: RefCell::new(None), history: HashMap::new(), event_tx, + conn_counter, } } @@ -783,7 +787,7 @@ mod tests { fn srv() -> Server { let (tx, _rx) = mpsc::channel(); - Server::new(Config::default(), tx) + Server::new(Config::default(), tx, Arc::new(AtomicU64::new(1))) } #[test]