From e200779d27a2a5760850d5b54f3dfad9198df73f Mon Sep 17 00:00:00 2001 From: reverse Date: Wed, 19 Aug 2026 01:15:26 +0000 Subject: [PATCH] =?UTF-8?q?connclass:=20resolve=20hash=3D=20independently?= =?UTF-8?q?=20of=20token=20order=20=E2=80=94=20the=20fold=20only=20ran=20i?= =?UTF-8?q?f=20password=3D=20had=20already=20been=20parsed,=20so=20"hash?= =?UTF-8?q?=3Dsha256=20password=3D"=20(hash=20first)=20stored=20the?= =?UTF-8?q?=20digest=20as=20a=20plaintext=20password=20and=20every=20login?= =?UTF-8?q?=20to=20that=20class=20failed;=20build()=20now=20combines=20:=20after=20the=20full=20token=20pass?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/modules/connclass.rs | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/modules/connclass.rs b/src/modules/connclass.rs index d1c43ca..d9a9971 100644 --- a/src/modules/connclass.rs +++ b/src/modules/connclass.rs @@ -69,18 +69,10 @@ fn apply(c: &mut ConnClass, k: &str, v: &str) { c.ssl_trusted = v.eq_ignore_ascii_case("trusted"); } "password" | "pass" => c.password = Some(v.to_string()), - "hash" => { - // name the algorithm of a hashed password: fold it into the stored - // credential (`:`) that verify() auto-detects, unless the - // password value already carries its own prefix. - if let Some(pw) = c.password.take() { - c.password = Some(if pw.contains(':') { - pw - } else { - format!("{v}:{pw}") - }); - } - } + // `hash=` names the algorithm of a hashed password; it's folded into the + // stored credential in build() *after* the whole token pass, so it works + // regardless of whether it appears before or after `password=`. + "hash" => {} "port" => c.ports.extend(list(v).filter_map(|p| p.parse::().ok())), "localmax" => c.localmax = v.parse().ok(), "globalmax" => c.globalmax = v.parse().ok(), @@ -146,11 +138,25 @@ fn build(s: &Server, name: &str) -> Option { resolvehostnames: true, ..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('=') { apply(&mut c, k, v); } } + // fold `:` 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() { c.allow.push("*".to_string()); // an unqualified class matches everyone }