gardesk/garcard / c293974

Browse files

Prefer session backend then fallback helper

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
c293974d0e065a71d94ecc2496e7d86f30c07944
Parents
ebbdce3
Tree
57b54f7

1 changed file

StatusFile+-
M garcard/src/polkit_helper.rs 120 15
garcard/src/polkit_helper.rsmodified
@@ -91,30 +91,58 @@ impl HelperSocketClient {
9191
         cookie: &str,
9292
         prompts: &mut P,
9393
     ) -> Result<HelperOutcome> {
94
+        let username_line = sanitize_control_line(username);
95
+        let cookie_line = sanitize_control_line(cookie);
9496
         let conversation_backend = helper_conversation_backend();
9597
         tracing::debug!(
9698
             backend = %conversation_backend.as_str(),
9799
             env_key = HELPER_CONVERSATION_BACKEND_ENV,
98100
             "Selected polkit helper conversation backend"
99101
         );
100
-        match conversation_backend {
101
-            HelperConversationBackend::Auto | HelperConversationBackend::HelperProtocol => {
102
-                self.authenticate_with_helper_protocol(username, cookie, prompts)
102
+        self.authenticate_with_backend(&username_line, &cookie_line, prompts, conversation_backend)
103
+    }
104
+
105
+    fn authenticate_with_backend<P: PromptProvider>(
106
+        &self,
107
+        username_line: &str,
108
+        cookie_line: &str,
109
+        prompts: &mut P,
110
+        backend: HelperConversationBackend,
111
+    ) -> Result<HelperOutcome> {
112
+        match backend {
113
+            HelperConversationBackend::HelperProtocol => {
114
+                self.authenticate_with_helper_protocol(username_line, cookie_line, prompts)
103115
             }
104116
             HelperConversationBackend::SessionApi => {
105
-                self.authenticate_via_session_api(username, cookie, prompts)
117
+                self.authenticate_via_session_api(username_line, cookie_line, prompts)
118
+            }
119
+            HelperConversationBackend::Auto => {
120
+                match self.authenticate_via_session_api(username_line, cookie_line, prompts) {
121
+                    Ok(outcome) => return Ok(outcome),
122
+                    Err(err) if is_session_api_unavailable_error(&err) => {
123
+                        tracing::debug!(
124
+                            error = %err,
125
+                            "Session-api conversation backend unavailable; falling back to helper protocol backend"
126
+                        );
127
+                    }
128
+                    Err(err) => {
129
+                        tracing::warn!(
130
+                            error = %err,
131
+                            "Session-api conversation backend failed; falling back to helper protocol backend"
132
+                        );
133
+                    }
134
+                }
135
+                self.authenticate_with_helper_protocol(username_line, cookie_line, prompts)
106136
             }
107137
         }
108138
     }
109139
 
110140
     fn authenticate_with_helper_protocol<P: PromptProvider>(
111141
         &self,
112
-        username: &str,
113
-        cookie: &str,
142
+        username_line: &str,
143
+        cookie_line: &str,
114144
         prompts: &mut P,
115145
     ) -> Result<HelperOutcome> {
116
-        let username_line = sanitize_control_line(username);
117
-        let cookie_line = sanitize_control_line(cookie);
118146
         let transport = helper_transport_mode();
119147
         tracing::debug!(
120148
             transport = %transport.as_str(),
@@ -161,11 +189,22 @@ impl HelperSocketClient {
161189
 
162190
     fn authenticate_via_session_api<P: PromptProvider>(
163191
         &self,
164
-        _username: &str,
165
-        _cookie: &str,
166
-        _prompts: &mut P,
192
+        username_line: &str,
193
+        cookie_line: &str,
194
+        prompts: &mut P,
167195
     ) -> Result<HelperOutcome> {
168
-        anyhow::bail!("session-api helper conversation backend is not yet implemented");
196
+        let helper = resolve_direct_helper_path()
197
+            .ok_or_else(|| anyhow::Error::new(SessionApiUnavailableError))?;
198
+        tracing::debug!(
199
+            helper = %helper.display(),
200
+            "Using session-api conversation backend via direct helper process"
201
+        );
202
+        self.authenticate_via_helper_process_with_helper(
203
+            &helper,
204
+            username_line,
205
+            cookie_line,
206
+            prompts,
207
+        )
169208
     }
170209
 
171210
     fn authenticate_auto_transport<P: PromptProvider>(
@@ -612,6 +651,23 @@ fn is_no_initial_helper_response_error(err: &anyhow::Error) -> bool {
612651
     err.downcast_ref::<NoInitialHelperResponseError>().is_some()
613652
 }
614653
 
654
+#[derive(Debug)]
655
+struct SessionApiUnavailableError;
656
+
657
+impl std::fmt::Display for SessionApiUnavailableError {
658
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
659
+        f.write_str(
660
+            "session-api helper conversation backend unavailable: no root setuid helper found",
661
+        )
662
+    }
663
+}
664
+
665
+impl std::error::Error for SessionApiUnavailableError {}
666
+
667
+fn is_session_api_unavailable_error(err: &anyhow::Error) -> bool {
668
+    err.downcast_ref::<SessionApiUnavailableError>().is_some()
669
+}
670
+
615671
 fn write_line(stream: &mut impl Write, value: &str) -> Result<()> {
616672
     stream.write_all(value.as_bytes())?;
617673
     stream.write_all(b"\n")?;
@@ -1003,18 +1059,67 @@ mod tests {
10031059
     }
10041060
 
10051061
     #[test]
1006
-    fn session_backend_reports_not_implemented() {
1062
+    fn session_backend_reports_unavailable_without_setuid_helper() {
10071063
         let client = HelperSocketClient::new(temp_socket_path());
10081064
         let mut prompts = FakePrompt::default();
10091065
         let err = client
10101066
             .authenticate_via_session_api("alice", "cookie-session", &mut prompts)
1011
-            .expect_err("session backend should be unimplemented");
1067
+            .expect_err("session backend should be unavailable");
10121068
         assert!(
10131069
             err.to_string()
1014
-                .contains("session-api helper conversation backend is not yet implemented")
1070
+                .contains("session-api helper conversation backend unavailable")
10151071
         );
10161072
     }
10171073
 
1074
+    #[test]
1075
+    fn auto_backend_falls_back_to_helper_protocol_when_session_unavailable() {
1076
+        let socket_path = temp_socket_path();
1077
+        let listener = UnixListener::bind(&socket_path).expect("bind test socket");
1078
+
1079
+        let server = thread::spawn(move || {
1080
+            let (mut stream, _) = listener.accept().expect("accept");
1081
+            let read_stream = stream.try_clone().expect("clone");
1082
+            let mut reader = BufReader::new(read_stream);
1083
+
1084
+            let mut first_line = String::new();
1085
+            reader.read_line(&mut first_line).expect("read first line");
1086
+            if first_line.trim() == "alice" {
1087
+                let mut cookie = String::new();
1088
+                reader.read_line(&mut cookie).expect("read cookie");
1089
+            }
1090
+
1091
+            stream
1092
+                .write_all(b"PAM_PROMPT_ECHO_OFF Password:\n")
1093
+                .expect("write prompt");
1094
+            stream.flush().expect("flush prompt");
1095
+
1096
+            let mut secret = String::new();
1097
+            reader.read_line(&mut secret).expect("read secret");
1098
+            stream.write_all(b"SUCCESS\n").expect("write success");
1099
+            stream.flush().expect("flush success");
1100
+        });
1101
+
1102
+        let client = HelperSocketClient::new(&socket_path);
1103
+        let mut prompts = FakePrompt {
1104
+            secret_response: PromptResponse::Submitted("correct horse".to_string()),
1105
+            ..FakePrompt::default()
1106
+        };
1107
+
1108
+        let outcome = client
1109
+            .authenticate_with_backend(
1110
+                "alice",
1111
+                "cookie-auto-fallback",
1112
+                &mut prompts,
1113
+                HelperConversationBackend::Auto,
1114
+            )
1115
+            .expect("auto backend fallback should succeed");
1116
+        assert_eq!(outcome, HelperOutcome::Authorized);
1117
+        assert_eq!(prompts.success_count, 1);
1118
+
1119
+        server.join().expect("server join");
1120
+        let _ = std::fs::remove_file(&socket_path);
1121
+    }
1122
+
10181123
     #[test]
10191124
     fn helper_client_drives_prompt_round_trip() {
10201125
         let socket_path = temp_socket_path();