webirc: restrict a gateway password to a source ip-mask; document webirc in the example config

This commit is contained in:
Jean Chevronnet 2026-08-08 20:02:22 +00:00
parent ad8d542e4e
commit b92101616b
No known key found for this signature in database
GPG key ID: 439666D63A9477E4
4 changed files with 26 additions and 13 deletions

View file

@ -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 = <password> [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 = <name> <password>
oper = admin CHANGE_THIS_PASSWORD

View file

@ -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 = <password> [gateway-name]
// webirc = <password> [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));
}
}
_ => {}

View file

@ -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) = (&params[0], &params[2], &params[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;

View file

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