sasl external: request tls client cert, plumb sha256 certfp through to user, relay to services; advertise sasl=PLAIN,EXTERNAL on tls

This commit is contained in:
Jean Chevronnet 2026-08-08 19:36:48 +00:00
parent 8d31feb4e7
commit 02528df5e9
7 changed files with 75 additions and 9 deletions

View file

@ -67,13 +67,18 @@ impl Command for Cap {
match params[0].to_ascii_uppercase().as_str() {
"LS" => {
let cap302 = params.get(1).map(|v| v == "302").unwrap_or(false);
let secure = s.users.get(&uid).map(|u| u.secure).unwrap_or(false);
if let Some(u) = s.users.get_mut(&uid) {
u.cap = true; // hold registration until CAP END
u.cap_302 |= cap302;
}
s.send(
uid,
format!(":{} CAP {who} LS :{}", s.name, Caps::ls_line(cap302)),
format!(
":{} CAP {who} LS :{}",
s.name,
Caps::ls_line(cap302, secure)
),
);
}
"REQ" => {
@ -177,6 +182,28 @@ impl Command for Authenticate {
}
s.send(uid, "AUTHENTICATE +".to_string());
CmdResult::Ok
} else if arg.eq_ignore_ascii_case("EXTERNAL") {
// CertFP: only works on TLS with a client cert; the fingerprint
// goes to services, which map it to an account.
let certfp = s.users.get(&uid).and_then(|u| u.certfp.clone());
match certfp {
Some(fp) if have_services => {
if let Some(u) = s.users.get_mut(&uid) {
u.sasl_mech = Some("EXTERNAL".to_string());
}
s.sasl_relay(uid, &format!("S EXTERNAL {fp}"));
s.send(uid, "AUTHENTICATE +".to_string());
CmdResult::Ok
}
_ => {
s.numeric(
uid,
ERR_SASLFAIL,
":SASL EXTERNAL requires a client certificate",
);
CmdResult::Fail
}
}
} else {
s.numeric(uid, RPL_SASLMECHS, "PLAIN :are available SASL mechanisms");
s.numeric(uid, ERR_SASLFAIL, ":Unsupported SASL mechanism");