connect: oper can dial a configured server link on demand

This commit is contained in:
Jean Chevronnet 2026-08-08 20:19:14 +00:00
parent eb7f9f25c4
commit 9e5f71f961
No known key found for this signature in database
GPG key ID: 439666D63A9477E4
4 changed files with 60 additions and 8 deletions

View file

@ -34,6 +34,7 @@ pub fn commands() -> Vec<Box<dyn Command>> {
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 <servername>`.
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 = &params[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<Uid> {
match s.find_nick(nick) {

View file

@ -66,9 +66,13 @@ pub struct Ircd {
}
impl Ircd {
pub fn new(cfg: Config, event_tx: Sender<Event>) -> Ircd {
pub fn new(
cfg: Config,
event_tx: Sender<Event>,
conn_counter: std::sync::Arc<std::sync::atomic::AtomicU64>,
) -> Ircd {
Ircd {
server: Server::new(cfg, event_tx),
server: Server::new(cfg, event_tx, conn_counter),
commands: command_table(),
modules: crate::modules::default_modules(),
}

View file

@ -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) {

View file

@ -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<Option<(Uid, Vec<String>)>>,
pub history: HashMap<String, VecDeque<HistMsg>>, // channel key -> recent messages (CHATHISTORY)
pub event_tx: Sender<Event>, // self-inject events (DNS results)
pub conn_counter: Arc<AtomicU64>, // mints connection uids (for CONNECT dials)
}
impl Server {
pub fn new(cfg: Config, event_tx: Sender<Event>) -> Server {
pub fn new(cfg: Config, event_tx: Sender<Event>, conn_counter: Arc<AtomicU64>) -> 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]