fix: security and correctness audit fixes (v0.4.1)

PAM conv callback: check msg_style (password only for ECHO_OFF),
handle strdup OOM with proper cleanup, null-check PAM handle.

Fingerprint: self-wire D-Bus g-signal in start() via Rc<RefCell<>>
and connect_local — VerifyStatus signals are now actually dispatched.
VerifyStop before VerifyStart in restart_verify.

Lockscreen: password entry stays active after faillock threshold
(PAM decides lockout, not UI), use Zeroizing<String> from GTK entry.

Release builds exit(1) without ext-session-lock-v1 support.

Config: fingerprint_enabled as Option<bool> so empty user config
does not override system config.

Dead code: remove unused i18n strings and fingerprint accessors,
parameterize faillock_warning max_attempts.
This commit is contained in:
2026-03-28 00:06:27 +01:00
parent 64f032cd9a
commit 17f8930ff7
8 changed files with 201 additions and 70 deletions
+7 -14
View File
@@ -10,13 +10,11 @@ const DEFAULT_LOCALE_CONF: &str = "/etc/locale.conf";
#[derive(Debug, Clone)]
pub struct Strings {
pub password_placeholder: &'static str,
pub unlock_button: &'static str,
pub reboot_tooltip: &'static str,
pub shutdown_tooltip: &'static str,
pub fingerprint_prompt: &'static str,
pub fingerprint_success: &'static str,
pub fingerprint_failed: &'static str,
pub auth_failed: &'static str,
pub wrong_password: &'static str,
pub reboot_failed: &'static str,
pub shutdown_failed: &'static str,
@@ -30,13 +28,11 @@ pub struct Strings {
const STRINGS_DE: Strings = Strings {
password_placeholder: "Passwort",
unlock_button: "Entsperren",
reboot_tooltip: "Neustart",
shutdown_tooltip: "Herunterfahren",
fingerprint_prompt: "Fingerabdruck auflegen zum Entsperren",
fingerprint_success: "Fingerabdruck erkannt",
fingerprint_failed: "Fingerabdruck nicht erkannt",
auth_failed: "Authentifizierung fehlgeschlagen",
wrong_password: "Falsches Passwort",
reboot_failed: "Neustart fehlgeschlagen",
shutdown_failed: "Herunterfahren fehlgeschlagen",
@@ -50,13 +46,11 @@ const STRINGS_DE: Strings = Strings {
const STRINGS_EN: Strings = Strings {
password_placeholder: "Password",
unlock_button: "Unlock",
reboot_tooltip: "Reboot",
shutdown_tooltip: "Shut down",
fingerprint_prompt: "Place finger on reader to unlock",
fingerprint_success: "Fingerprint recognized",
fingerprint_failed: "Fingerprint not recognized",
auth_failed: "Authentication failed",
wrong_password: "Wrong password",
reboot_failed: "Reboot failed",
shutdown_failed: "Shutdown failed",
@@ -96,10 +90,9 @@ pub fn load_strings(locale: Option<&str>) -> &'static Strings {
match locale.as_str() { "de" => &STRINGS_DE, _ => &STRINGS_EN }
}
pub fn faillock_warning(attempt_count: u32, strings: &Strings) -> Option<String> {
const MAX: u32 = 3;
if attempt_count >= MAX { return Some(strings.faillock_locked.to_string()); }
let remaining = MAX - attempt_count;
pub fn faillock_warning(attempt_count: u32, max_attempts: u32, strings: &Strings) -> Option<String> {
if attempt_count >= max_attempts { return Some(strings.faillock_locked.to_string()); }
let remaining = max_attempts - attempt_count;
if remaining == 1 { return Some(strings.faillock_attempts_remaining.replace("{n}", &remaining.to_string())); }
None
}
@@ -133,8 +126,8 @@ mod tests {
assert!(!s.fingerprint_failed.is_empty());
}
#[test] fn faillock_zero() { assert!(faillock_warning(0, load_strings(Some("en"))).is_none()); }
#[test] fn faillock_one() { assert!(faillock_warning(1, load_strings(Some("en"))).is_none()); }
#[test] fn faillock_two() { assert!(faillock_warning(2, load_strings(Some("en"))).is_some()); }
#[test] fn faillock_three() { assert_eq!(faillock_warning(3, load_strings(Some("en"))).unwrap(), "Account may be locked"); }
#[test] fn faillock_zero() { assert!(faillock_warning(0, 3, load_strings(Some("en"))).is_none()); }
#[test] fn faillock_one() { assert!(faillock_warning(1, 3, load_strings(Some("en"))).is_none()); }
#[test] fn faillock_two() { assert!(faillock_warning(2, 3, load_strings(Some("en"))).is_some()); }
#[test] fn faillock_three() { assert_eq!(faillock_warning(3, 3, load_strings(Some("en"))).unwrap(), "Account may be locked"); }
}