oper: TLS client-cert fingerprint login — oper block gains an optional fp=<sha256>; password=* means cert-only. Named OperBlock struct replaces the (name,pass,level) tuple. (Password login was never broken — verified live.)

This commit is contained in:
Jean Chevronnet 2026-08-18 22:55:07 +00:00
parent 853be58d18
commit c4456cf002
6 changed files with 66 additions and 15 deletions

View file

@ -5,7 +5,9 @@
//! network = echoNet
//! bind = 127.0.0.1:6767
//! motd = Welcome to echoIRCd
//! oper = god secret
//! oper = god secret # name + password (+ optional level)
//! oper = god * fp=<sha256-cert-fp> # cert-only login (no password)
//! oper = god secret fp=<sha256-cert-fp> # password AND matching cert
//! ```
use crate::map::HashMap;
@ -29,6 +31,17 @@ pub struct LinkBlock {
pub autoconnect: bool,
}
/// An oper login: `oper = <name> <password|*> [level] [fp=<sha256-fingerprint>]`.
/// `password = *` means no password is checked (cert-only login); a `fp=` token
/// requires the user's TLS client-certificate SHA-256 fingerprint to match.
#[derive(Clone, Default)]
pub struct OperBlock {
pub name: String,
pub password: String,
pub level: u32,
pub fingerprint: Option<String>,
}
/// Config for the `antimixedutf8` module (blocks mixed-script look-alike spam).
#[derive(Clone)]
pub struct AntiMixedCfg {
@ -70,7 +83,7 @@ pub struct Config {
pub tls_cert: Option<String>, // PEM certificate chain
pub tls_key: Option<String>, // PEM private key
pub motd: Vec<String>,
pub opers: Vec<(String, String, u32)>, // (name, password, operlevel)
pub opers: Vec<OperBlock>, // oper logins (see OperBlock)
pub cloak_key: Option<String>, // 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
@ -197,8 +210,24 @@ impl Config {
"oper" => {
let mut it = v.split_whitespace();
if let (Some(n), Some(p)) = (it.next(), it.next()) {
let level = it.next().and_then(|l| l.parse().ok()).unwrap_or(0);
c.opers.push((n.to_string(), p.to_string(), level));
let mut b = OperBlock {
name: n.to_string(),
password: p.to_string(),
level: 0,
fingerprint: None,
};
// trailing tokens (any order): a number is the operlevel, a
// `fp=`/`certfp=` token is the required TLS cert fingerprint.
for tok in it {
if let Some(fp) =
tok.strip_prefix("fp=").or_else(|| tok.strip_prefix("certfp="))
{
b.fingerprint = Some(fp.to_ascii_lowercase());
} else if let Ok(l) = tok.parse::<u32>() {
b.level = l;
}
}
c.opers.push(b);
}
}
// +G censor word: `badword = <find> [replace]` (no replace ⇒ block)