connclass: resolve hash= independently of token order — the fold only ran if password= had already been parsed, so "hash=sha256 password=<hex>" (hash first) stored the digest as a plaintext password and every login to that class failed; build() now combines <algo>:<digest> after the full token pass

This commit is contained in:
Jean Chevronnet 2026-08-19 01:15:26 +00:00
parent 928026c49e
commit e200779d27

View file

@ -69,18 +69,10 @@ fn apply(c: &mut ConnClass, k: &str, v: &str) {
c.ssl_trusted = v.eq_ignore_ascii_case("trusted"); c.ssl_trusted = v.eq_ignore_ascii_case("trusted");
} }
"password" | "pass" => c.password = Some(v.to_string()), "password" | "pass" => c.password = Some(v.to_string()),
"hash" => { // `hash=` names the algorithm of a hashed password; it's folded into the
// name the algorithm of a hashed password: fold it into the stored // stored credential in build() *after* the whole token pass, so it works
// credential (`<algo>:<digest>`) that verify() auto-detects, unless the // regardless of whether it appears before or after `password=`.
// password value already carries its own prefix. "hash" => {}
if let Some(pw) = c.password.take() {
c.password = Some(if pw.contains(':') {
pw
} else {
format!("{v}:{pw}")
});
}
}
"port" => c.ports.extend(list(v).filter_map(|p| p.parse::<u16>().ok())), "port" => c.ports.extend(list(v).filter_map(|p| p.parse::<u16>().ok())),
"localmax" => c.localmax = v.parse().ok(), "localmax" => c.localmax = v.parse().ok(),
"globalmax" => c.globalmax = v.parse().ok(), "globalmax" => c.globalmax = v.parse().ok(),
@ -146,11 +138,25 @@ fn build(s: &Server, name: &str) -> Option<ConnClass> {
resolvehostnames: true, resolvehostnames: true,
..Default::default() ..Default::default()
}; };
for tok in toks { // resolve the hash algorithm independently of token order (see `apply`'s `hash`)
let hash_algo = toks
.iter()
.find_map(|t| t.strip_prefix("hash="))
.map(str::to_string);
for tok in &toks {
if let Some((k, v)) = tok.split_once('=') { if let Some((k, v)) = tok.split_once('=') {
apply(&mut c, k, v); apply(&mut c, k, v);
} }
} }
// fold `<algo>:<digest>` into the credential verify() auto-detects, unless the
// password already carries its own prefix.
if let (Some(algo), Some(pw)) = (hash_algo, c.password.take()) {
c.password = Some(if pw.contains(':') {
pw
} else {
format!("{algo}:{pw}")
});
}
if c.allow.is_empty() { if c.allow.is_empty() {
c.allow.push("*".to_string()); // an unqualified class matches everyone c.allow.push("*".to_string()); // an unqualified class matches everyone
} }