diff --git a/src/config.rs b/src/config.rs index 5bb05e5..0f3db98 100644 --- a/src/config.rs +++ b/src/config.rs @@ -75,6 +75,7 @@ pub struct Config { pub dnsbl_action: String, // mark | kline | gline | zline (on a hit) pub dnsbl_reason: String, // ban reason for a DNSBL hit pub sasl_server: String, // linked services server that handles SASL ("" = none) + pub webirc: Vec<(String, String)>, // trusted web gateways: (password, gateway name) } impl Default for Config { @@ -102,6 +103,7 @@ impl Default for Config { dnsbl_action: "mark".to_string(), dnsbl_reason: "Your host is listed in a DNS blocklist".to_string(), sasl_server: String::new(), + webirc: Vec::new(), } } } @@ -235,6 +237,14 @@ impl Config { "dnsbl_action" => c.dnsbl_action = v.to_ascii_lowercase(), "dnsbl_reason" => c.dnsbl_reason = v.to_string(), "sasl_server" | "sasl_target" => c.sasl_server = v.to_string(), + "webirc" => { + // webirc = [gateway-name] + let mut it = v.split_whitespace(); + if let Some(pass) = it.next() { + let gw = it.next().unwrap_or("webirc").to_string(); + c.webirc.push((pass.to_string(), gw)); + } + } _ => {} } } diff --git a/src/coremods/core_user.rs b/src/coremods/core_user.rs index 0d6615f..0b8964f 100644 --- a/src/coremods/core_user.rs +++ b/src/coremods/core_user.rs @@ -1,6 +1,8 @@ //! core_user — the client registration & session commands: CAP, NICK, USER, //! PING, PONG, QUIT. +use std::net::{IpAddr, SocketAddr}; + use crate::command::{CmdResult, Command}; use crate::numeric::*; use crate::server::Server; @@ -18,9 +20,52 @@ pub fn commands() -> Vec> { Box::new(Quit), Box::new(Away), Box::new(SetName), + Box::new(WebIrc), ] } +/// WEBIRC — a trusted web gateway declares the real client's host + IP, so users +/// behind it don't all share the gateway's address. `WEBIRC +/// [:flags]`; must precede registration and the password must +/// match a `webirc` config block. (Password-only trust for now — restricting it +/// to the gateway's own source IP is a TODO.) +struct WebIrc; +impl Command for WebIrc { + fn name(&self) -> &'static str { + "WEBIRC" + } + fn min_params(&self) -> usize { + 4 + } + fn before_reg(&self) -> bool { + true + } + fn handle(&self, s: &mut Server, uid: Uid, params: &[String]) -> CmdResult { + if s.users.get(&uid).map(|u| u.registered).unwrap_or(false) { + return CmdResult::Fail; // can't re-spoof a registered session + } + let (pass, host, ip) = (¶ms[0], ¶ms[2], ¶ms[3]); + let Some(gw) = s + .webirc + .iter() + .find(|(p, _)| p == pass) + .map(|(_, g)| g.clone()) + else { + s.notice_star(uid, "WEBIRC: invalid credentials"); + return CmdResult::Fail; + }; + let newip = ip.parse::().ok(); + if let Some(u) = s.users.get_mut(&uid) { + u.host = host.clone(); + if let Some(a) = newip { + u.addr = SocketAddr::new(a, u.addr.port()); + } + } + s.notice_star(uid, &format!("WEBIRC identity accepted via {gw}")); + CmdResult::Ok + } +} + struct Away; impl Command for Away { fn name(&self) -> &'static str { diff --git a/src/server.rs b/src/server.rs index 531f21f..3f4912f 100644 --- a/src/server.rs +++ b/src/server.rs @@ -104,6 +104,7 @@ pub struct Server { pub dnsbl_action: String, // mark | kline | gline | zline pub dnsbl_reason: String, // ban reason on a DNSBL hit pub sasl_server: String, // services server that handles SASL + pub webirc: Vec<(String, String)>, // trusted web gateways: (password, name) pub event_tx: Sender, // self-inject events (DNS results) } @@ -144,6 +145,7 @@ impl Server { dnsbl_action: cfg.dnsbl_action, dnsbl_reason: cfg.dnsbl_reason, sasl_server: cfg.sasl_server, + webirc: cfg.webirc, event_tx, } }