gardesk/garlock / 05cfe0d

Browse files

Add PAM authentication module

- Background thread for non-blocking auth
- Password verification via system-auth PAM stack
- Requires /etc/pam.d/garlock config
Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
05cfe0d7941616bc4257d3dc596f4d2e449c5ad3
Parents
22e857e
Tree
50351ef

1 changed file

StatusFile+-
A garlock/src/auth.rs 117 0
garlock/src/auth.rsadded
@@ -0,0 +1,117 @@
1
+//! PAM authentication for garlock
2
+//!
3
+//! Handles password verification against PAM to unlock the screen.
4
+//! Authentication runs in a background thread to keep the UI responsive.
5
+
6
+use anyhow::{anyhow, Result};
7
+use pam::Client;
8
+use std::sync::mpsc::{self, Receiver, TryRecvError};
9
+use std::thread;
10
+
11
+/// PAM service name (must match /etc/pam.d/garlock)
12
+const PAM_SERVICE: &str = "garlock";
13
+
14
+/// Result of an authentication attempt
15
+#[derive(Debug)]
16
+pub enum AuthResult {
17
+    /// Authentication succeeded
18
+    Success,
19
+    /// Authentication failed
20
+    Failure(String),
21
+}
22
+
23
+/// Handle for a pending authentication operation
24
+pub struct PendingAuth {
25
+    receiver: Receiver<AuthResult>,
26
+}
27
+
28
+impl PendingAuth {
29
+    /// Check if authentication has completed (non-blocking)
30
+    ///
31
+    /// Returns `Some(result)` if complete, `None` if still pending.
32
+    pub fn try_recv(&self) -> Option<AuthResult> {
33
+        match self.receiver.try_recv() {
34
+            Ok(result) => Some(result),
35
+            Err(TryRecvError::Empty) => None,
36
+            Err(TryRecvError::Disconnected) => {
37
+                // Thread died unexpectedly
38
+                Some(AuthResult::Failure("Authentication thread died".to_string()))
39
+            }
40
+        }
41
+    }
42
+}
43
+
44
+/// Get the current username
45
+pub fn get_current_username() -> Result<String> {
46
+    users::get_current_username()
47
+        .and_then(|s| s.into_string().ok())
48
+        .ok_or_else(|| anyhow!("Failed to get current username"))
49
+}
50
+
51
+/// Start authentication in a background thread
52
+///
53
+/// Returns a handle that can be polled for the result.
54
+pub fn authenticate_async(username: String, password: String) -> PendingAuth {
55
+    let (tx, rx) = mpsc::channel();
56
+
57
+    thread::spawn(move || {
58
+        let result = authenticate_blocking(&username, &password);
59
+        // Ignore send errors - receiver may have been dropped
60
+        let _ = tx.send(result);
61
+    });
62
+
63
+    PendingAuth { receiver: rx }
64
+}
65
+
66
+/// Perform PAM authentication (blocking)
67
+///
68
+/// This should be called from a background thread to avoid blocking the UI.
69
+fn authenticate_blocking(username: &str, password: &str) -> AuthResult {
70
+    tracing::debug!(%username, "Starting PAM authentication");
71
+
72
+    match pam_authenticate(username, password) {
73
+        Ok(()) => {
74
+            tracing::info!(%username, "PAM authentication succeeded");
75
+            AuthResult::Success
76
+        }
77
+        Err(e) => {
78
+            tracing::warn!(%username, error = %e, "PAM authentication failed");
79
+            AuthResult::Failure(e.to_string())
80
+        }
81
+    }
82
+}
83
+
84
+/// Low-level PAM authentication
85
+fn pam_authenticate(username: &str, password: &str) -> Result<()> {
86
+    // Create PAM client with password conversation handler
87
+    let mut client = Client::with_password(PAM_SERVICE)
88
+        .map_err(|e| anyhow!("Failed to create PAM client: {:?}", e))?;
89
+
90
+    // Set credentials for the conversation
91
+    client
92
+        .conversation_mut()
93
+        .set_credentials(username, password);
94
+
95
+    // Authenticate the user
96
+    // Note: For a screen locker, we only need to verify the password.
97
+    // We don't need to open a session since one already exists.
98
+    client
99
+        .authenticate()
100
+        .map_err(|e| anyhow!("Authentication failed: {:?}", e))?;
101
+
102
+    Ok(())
103
+}
104
+
105
+#[cfg(test)]
106
+mod tests {
107
+    use super::*;
108
+
109
+    #[test]
110
+    fn test_get_current_username() {
111
+        // Should succeed in most environments
112
+        let result = get_current_username();
113
+        assert!(result.is_ok());
114
+        let username = result.unwrap();
115
+        assert!(!username.is_empty());
116
+    }
117
+}