webirc: trusted web gateways can set the real client host/ip (webirc config blocks)

This commit is contained in:
Jean Chevronnet 2026-08-08 19:42:10 +00:00
parent 8edb7b2ab2
commit 81e9728cfa
3 changed files with 57 additions and 0 deletions

View file

@ -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 = <password> [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));
}
}
_ => {}
}
}

View file

@ -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<dyn Command>> {
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 <password> <gateway>
/// <hostname> <ip> [: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) = (&params[0], &params[2], &params[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::<IpAddr>().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 {

View file

@ -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<Event>, // 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,
}
}