gardesk/garcard / 138e17a

Browse files

Auto-detect helper socket protocol

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
138e17a63b86ff836b68fec14abfdac3cff91306
Parents
4d745ea
Tree
7ba2975

1 changed file

StatusFile+-
M garcard/src/polkit_helper.rs 138 23
garcard/src/polkit_helper.rsmodified
@@ -1,13 +1,15 @@
11
 use anyhow::{Context, Result};
2
-use std::io::{BufRead, BufReader, Write};
2
+use std::io::{BufRead, BufReader, ErrorKind, Write};
33
 use std::os::unix::fs::{MetadataExt, PermissionsExt};
44
 use std::os::unix::net::UnixStream;
55
 use std::path::{Path, PathBuf};
66
 use std::process::{Command, Stdio};
7
+use std::time::Duration;
78
 
89
 pub const DEFAULT_HELPER_SOCKET: &str = "/run/polkit/agent-helper.socket";
910
 const HELPER_TRANSPORT_ENV: &str = "GARCARD_POLKIT_HELPER_TRANSPORT";
1011
 const HELPER_SOCKET_PROTOCOL_ENV: &str = "GARCARD_POLKIT_SOCKET_PROTOCOL";
12
+const SOCKET_FIRST_RESPONSE_TIMEOUT: Duration = Duration::from_millis(500);
1113
 
1214
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
1315
 pub enum HelperOutcome {
@@ -84,7 +86,6 @@ impl HelperSocketClient {
8486
         let username_line = sanitize_control_line(username);
8587
         let cookie_line = sanitize_control_line(cookie);
8688
         let transport = helper_transport_mode();
87
-        let protocol = helper_socket_protocol();
8889
         tracing::debug!(
8990
             transport = %transport.as_str(),
9091
             env_key = HELPER_TRANSPORT_ENV,
@@ -104,6 +105,67 @@ impl HelperSocketClient {
104105
             );
105106
         }
106107
 
108
+        match helper_socket_protocol() {
109
+            HelperSocketProtocol::Auto => {
110
+                self.authenticate_via_socket_auto(&username_line, &cookie_line, prompts)
111
+            }
112
+            protocol => {
113
+                match self.authenticate_via_socket(&username_line, &cookie_line, prompts, protocol)
114
+                {
115
+                    Ok(outcome) => Ok(outcome),
116
+                    Err(err) if is_no_session_cookie_error(&err) => {
117
+                        prompts
118
+                            .auth_failed("Authentication failed")
119
+                            .context("prompt failure callback failed")?;
120
+                        Ok(HelperOutcome::Denied)
121
+                    }
122
+                    Err(err) => Err(err),
123
+                }
124
+            }
125
+        }
126
+    }
127
+
128
+    fn authenticate_via_socket_auto<P: PromptProvider>(
129
+        &self,
130
+        username: &str,
131
+        cookie: &str,
132
+        prompts: &mut P,
133
+    ) -> Result<HelperOutcome> {
134
+        let protocols = [
135
+            HelperSocketProtocol::UsernameCookie,
136
+            HelperSocketProtocol::CookieOnly,
137
+        ];
138
+
139
+        for protocol in protocols {
140
+            match self.authenticate_via_socket(username, cookie, prompts, protocol) {
141
+                Ok(outcome) => return Ok(outcome),
142
+                Err(err)
143
+                    if is_no_session_cookie_error(&err)
144
+                        || is_no_initial_helper_response_error(&err) =>
145
+                {
146
+                    tracing::warn!(
147
+                        protocol = %protocol.as_str(),
148
+                        error = %err,
149
+                        "Socket helper protocol attempt failed; retrying with alternate socket protocol"
150
+                    );
151
+                }
152
+                Err(err) => return Err(err),
153
+            }
154
+        }
155
+
156
+        prompts
157
+            .auth_failed("Authentication failed")
158
+            .context("prompt failure callback failed")?;
159
+        Ok(HelperOutcome::Denied)
160
+    }
161
+
162
+    fn authenticate_via_socket<P: PromptProvider>(
163
+        &self,
164
+        username_line: &str,
165
+        cookie_line: &str,
166
+        prompts: &mut P,
167
+        protocol: HelperSocketProtocol,
168
+    ) -> Result<HelperOutcome> {
107169
         let mut stream = UnixStream::connect(&self.socket_path).with_context(|| {
108170
             format!(
109171
                 "failed to connect to polkit helper socket at {}",
@@ -119,18 +181,10 @@ impl HelperSocketClient {
119181
             protocol = %protocol.as_str(),
120182
             "Connected to polkit helper socket"
121183
         );
122
-        if username_line.len() != username.len() || cookie_line.len() != cookie.len() {
123
-            tracing::debug!(
124
-                original_username_len = username.len(),
125
-                normalized_username_len = username_line.len(),
126
-                original_cookie_len = cookie.len(),
127
-                normalized_cookie_len = cookie_line.len(),
128
-                "Normalized helper auth control lines before send"
129
-            );
130
-        }
131184
         let read_stream = stream
132185
             .try_clone()
133186
             .context("failed to clone helper socket stream")?;
187
+        let _ = read_stream.set_read_timeout(Some(SOCKET_FIRST_RESPONSE_TIMEOUT));
134188
         let mut reader = BufReader::new(read_stream);
135189
 
136190
         if matches!(protocol, HelperSocketProtocol::UsernameCookie) {
@@ -139,14 +193,30 @@ impl HelperSocketClient {
139193
         write_line(&mut stream, &cookie_line).context("failed to send helper cookie")?;
140194
 
141195
         let mut saw_no_session_cookie = false;
196
+        let mut saw_first_event = false;
142197
         loop {
143198
             let mut line = String::new();
144
-            let bytes = reader
145
-                .read_line(&mut line)
146
-                .context("failed to read helper response line")?;
199
+            let bytes = match reader.read_line(&mut line) {
200
+                Ok(bytes) => bytes,
201
+                Err(err)
202
+                    if !saw_first_event
203
+                        && matches!(err.kind(), ErrorKind::TimedOut | ErrorKind::WouldBlock) =>
204
+                {
205
+                    return Err(NoInitialHelperResponseError.into());
206
+                }
207
+                Err(err) => {
208
+                    return Err(
209
+                        anyhow::Error::new(err).context("failed to read helper response line")
210
+                    );
211
+                }
212
+            };
147213
             if bytes == 0 {
148214
                 anyhow::bail!("helper closed connection unexpectedly");
149215
             }
216
+            if !saw_first_event {
217
+                saw_first_event = true;
218
+                let _ = reader.get_mut().set_read_timeout(None);
219
+            }
150220
             tracing::debug!(
151221
                 helper_line = %line.trim_end_matches('\n').trim_end_matches('\r'),
152222
                 "Received helper protocol line"
@@ -239,9 +309,7 @@ impl HelperSocketClient {
239309
                                 prompts,
240310
                             );
241311
                         }
242
-                        tracing::warn!(
243
-                            "Socket helper reported no session for cookie and no viable direct helper is available; treating as authentication failure"
244
-                        );
312
+                        return Err(NoSessionForCookieError.into());
245313
                     }
246314
                     prompts
247315
                         .auth_failed("Authentication failed")
@@ -378,6 +446,36 @@ impl HelperSocketClient {
378446
     }
379447
 }
380448
 
449
+#[derive(Debug)]
450
+struct NoSessionForCookieError;
451
+
452
+impl std::fmt::Display for NoSessionForCookieError {
453
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
454
+        f.write_str("socket helper reported no session for cookie")
455
+    }
456
+}
457
+
458
+impl std::error::Error for NoSessionForCookieError {}
459
+
460
+fn is_no_session_cookie_error(err: &anyhow::Error) -> bool {
461
+    err.downcast_ref::<NoSessionForCookieError>().is_some()
462
+}
463
+
464
+#[derive(Debug)]
465
+struct NoInitialHelperResponseError;
466
+
467
+impl std::fmt::Display for NoInitialHelperResponseError {
468
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
469
+        f.write_str("socket helper produced no initial response")
470
+    }
471
+}
472
+
473
+impl std::error::Error for NoInitialHelperResponseError {}
474
+
475
+fn is_no_initial_helper_response_error(err: &anyhow::Error) -> bool {
476
+    err.downcast_ref::<NoInitialHelperResponseError>().is_some()
477
+}
478
+
381479
 fn write_line(stream: &mut impl Write, value: &str) -> Result<()> {
382480
     stream.write_all(value.as_bytes())?;
383481
     stream.write_all(b"\n")?;
@@ -467,6 +565,7 @@ impl HelperTransportMode {
467565
 
468566
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
469567
 enum HelperSocketProtocol {
568
+    Auto,
470569
     CookieOnly,
471570
     UsernameCookie,
472571
 }
@@ -474,6 +573,7 @@ enum HelperSocketProtocol {
474573
 impl HelperSocketProtocol {
475574
     fn as_str(self) -> &'static str {
476575
         match self {
576
+            Self::Auto => "socket-activated-auto",
477577
             Self::CookieOnly => "socket-activated-cookie-only",
478578
             Self::UsernameCookie => "socket-activated-username-cookie",
479579
         }
@@ -497,10 +597,13 @@ fn helper_socket_protocol() -> HelperSocketProtocol {
497597
         .map(|value| value.trim().to_ascii_lowercase())
498598
         .as_deref()
499599
     {
600
+        Some("cookie-only") | Some("cookie_only") | Some("cookieonly") => {
601
+            HelperSocketProtocol::CookieOnly
602
+        }
500603
         Some("username-cookie") | Some("username_cookie") | Some("usernamecookie") => {
501604
             HelperSocketProtocol::UsernameCookie
502605
         }
503
-        _ => HelperSocketProtocol::CookieOnly,
606
+        _ => HelperSocketProtocol::Auto,
504607
     }
505608
 }
506609
 
@@ -683,8 +786,16 @@ mod tests {
683786
             let read_stream = stream.try_clone().expect("clone");
684787
             let mut reader = BufReader::new(read_stream);
685788
 
686
-            let mut cookie = String::new();
687
-            reader.read_line(&mut cookie).expect("read cookie");
789
+            let mut first_line = String::new();
790
+            reader.read_line(&mut first_line).expect("read first line");
791
+            let first = first_line.trim().to_string();
792
+            let cookie = if first == "alice" {
793
+                let mut cookie = String::new();
794
+                reader.read_line(&mut cookie).expect("read cookie");
795
+                cookie.trim().to_string()
796
+            } else {
797
+                first
798
+            };
688799
 
689800
             stream
690801
                 .write_all(b"PAM_PROMPT_ECHO_OFF Password:\n")
@@ -696,7 +807,7 @@ mod tests {
696807
 
697808
             {
698809
                 let mut lines = transcript_for_thread.lock().expect("lock transcript");
699
-                lines.push(cookie.trim().to_string());
810
+                lines.push(cookie);
700811
                 lines.push(secret.trim().to_string());
701812
             }
702813
 
@@ -734,8 +845,12 @@ mod tests {
734845
             let read_stream = stream.try_clone().expect("clone");
735846
             let mut reader = BufReader::new(read_stream);
736847
 
737
-            let mut cookie = String::new();
738
-            reader.read_line(&mut cookie).expect("read cookie");
848
+            let mut first_line = String::new();
849
+            reader.read_line(&mut first_line).expect("read first line");
850
+            if first_line.trim() == "alice" {
851
+                let mut cookie = String::new();
852
+                reader.read_line(&mut cookie).expect("read cookie");
853
+            }
739854
 
740855
             stream
741856
                 .write_all(b"PAM_PROMPT_ECHO_OFF Password:\n")