From 49a1bf08b9bb8a25ee55263e005d796fc5395756 Mon Sep 17 00:00:00 2001 From: reverse Date: Sat, 8 Aug 2026 20:02:22 +0000 Subject: [PATCH] webirc: restrict a gateway password to a source ip-mask; document webirc in the example config --- echoircd.conf.example | 5 +++++ src/config.rs | 21 +++++++++++---------- src/coremods/core_user.rs | 11 +++++++++-- src/server.rs | 2 +- 4 files changed, 26 insertions(+), 13 deletions(-) diff --git a/echoircd.conf.example b/echoircd.conf.example index 5d92456..c3c9e1c 100644 --- a/echoircd.conf.example +++ b/echoircd.conf.example @@ -29,6 +29,11 @@ bind_server = 0.0.0.0:7000 # TLS with a client certificate (its fingerprint is sent to services). # sasl_server = services.example.net +# trusted web gateways (CGI:IRC / kiwiirc-style): they send WEBIRC to declare the +# real client's host+ip. webirc = [gateway-name] [ip-mask]; the ip-mask +# restricts which source IP may use the password (recommended). Repeat for more. +# webirc = CHANGE_THIS_WEBIRC_SECRET mygateway 203.0.113.9 + # IRC operators — oper = oper = admin CHANGE_THIS_PASSWORD diff --git a/src/config.rs b/src/config.rs index 0f3db98..9081571 100644 --- a/src/config.rs +++ b/src/config.rs @@ -60,13 +60,13 @@ pub struct Config { pub tls_cert: Option, // PEM certificate chain pub tls_key: Option, // PEM private key pub motd: Vec, - pub opers: Vec<(String, String)>, // (name, password) - pub cloak_key: Option, // secret key for host cloaking (+x); None = off - pub sid: String, // this server's 3-char server id (S2S) - pub serverdesc: String, // this server's description - pub bind_server: Option, // the server-to-server link listener - pub links: Vec, // peers we accept / dial - pub conf_path: String, // where this was loaded from (for REHASH) + pub opers: Vec<(String, String)>, // (name, password) + pub cloak_key: Option, // secret key for host cloaking (+x); None = off + pub sid: String, // this server's 3-char server id (S2S) + pub serverdesc: String, // this server's description + pub bind_server: Option, // the server-to-server link listener + pub links: Vec, // peers we accept / dial + pub conf_path: String, // where this was loaded from (for REHASH) pub censor: Vec<(String, String)>, // +G bad words: (find, replace); empty replace = block pub amu: AntiMixedCfg, // antimixedutf8 module config pub resolve_hosts: bool, // reverse-DNS clients on connect (default on) @@ -75,7 +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) + pub webirc: Vec<(String, String, String)>, // web gateways: (password, name, ip-mask) } impl Default for Config { @@ -238,11 +238,12 @@ impl Config { "dnsbl_reason" => c.dnsbl_reason = v.to_string(), "sasl_server" | "sasl_target" => c.sasl_server = v.to_string(), "webirc" => { - // webirc = [gateway-name] + // webirc = [gateway-name] [ip-mask] 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)); + let mask = it.next().unwrap_or("").to_string(); + c.webirc.push((pass.to_string(), gw, mask)); } } _ => {} diff --git a/src/coremods/core_user.rs b/src/coremods/core_user.rs index 0b8964f..523c38e 100644 --- a/src/coremods/core_user.rs +++ b/src/coremods/core_user.rs @@ -3,6 +3,7 @@ use std::net::{IpAddr, SocketAddr}; +use crate::channels::glob_match; use crate::command::{CmdResult, Command}; use crate::numeric::*; use crate::server::Server; @@ -45,11 +46,17 @@ impl Command for WebIrc { return CmdResult::Fail; // can't re-spoof a registered session } let (pass, host, ip) = (¶ms[0], ¶ms[2], ¶ms[3]); + // the gateway's own connecting IP (before we spoof it below) + let from = s + .users + .get(&uid) + .map(|u| u.addr.ip().to_string()) + .unwrap_or_default(); let Some(gw) = s .webirc .iter() - .find(|(p, _)| p == pass) - .map(|(_, g)| g.clone()) + .find(|(p, _, mask)| p == pass && (mask.is_empty() || glob_match(mask, &from))) + .map(|(_, g, _)| g.clone()) else { s.notice_star(uid, "WEBIRC: invalid credentials"); return CmdResult::Fail; diff --git a/src/server.rs b/src/server.rs index fe3a6c5..dabd7ed 100644 --- a/src/server.rs +++ b/src/server.rs @@ -140,7 +140,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 webirc: Vec<(String, String, String)>, // web gateways: (password, name, ip-mask) // labeled-response: while Some((uid, buf)), that client's own responses are // diverted into `buf` instead of the socket, so `on_line` can wrap them with // the command's `label` (single tag, BATCH, or ACK). RefCell because the