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 {
91
         cookie: &str,
91
         cookie: &str,
92
         prompts: &mut P,
92
         prompts: &mut P,
93
     ) -> Result<HelperOutcome> {
93
     ) -> Result<HelperOutcome> {
94
+        let username_line = sanitize_control_line(username);
95
+        let cookie_line = sanitize_control_line(cookie);
94
         let conversation_backend = helper_conversation_backend();
96
         let conversation_backend = helper_conversation_backend();
95
         tracing::debug!(
97
         tracing::debug!(
96
             backend = %conversation_backend.as_str(),
98
             backend = %conversation_backend.as_str(),
97
             env_key = HELPER_CONVERSATION_BACKEND_ENV,
99
             env_key = HELPER_CONVERSATION_BACKEND_ENV,
98
             "Selected polkit helper conversation backend"
100
             "Selected polkit helper conversation backend"
99
         );
101
         );
100
-        match conversation_backend {
102
+        self.authenticate_with_backend(&username_line, &cookie_line, prompts, conversation_backend)
101
-            HelperConversationBackend::Auto | HelperConversationBackend::HelperProtocol => {
103
+    }
102
-                self.authenticate_with_helper_protocol(username, cookie, prompts)
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)
103
             }
115
             }
104
             HelperConversationBackend::SessionApi => {
116
             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)
106
             }
136
             }
107
         }
137
         }
108
     }
138
     }
109
 
139
 
110
     fn authenticate_with_helper_protocol<P: PromptProvider>(
140
     fn authenticate_with_helper_protocol<P: PromptProvider>(
111
         &self,
141
         &self,
112
-        username: &str,
142
+        username_line: &str,
113
-        cookie: &str,
143
+        cookie_line: &str,
114
         prompts: &mut P,
144
         prompts: &mut P,
115
     ) -> Result<HelperOutcome> {
145
     ) -> Result<HelperOutcome> {
116
-        let username_line = sanitize_control_line(username);
117
-        let cookie_line = sanitize_control_line(cookie);
118
         let transport = helper_transport_mode();
146
         let transport = helper_transport_mode();
119
         tracing::debug!(
147
         tracing::debug!(
120
             transport = %transport.as_str(),
148
             transport = %transport.as_str(),
@@ -161,11 +189,22 @@ impl HelperSocketClient {
161
 
189
 
162
     fn authenticate_via_session_api<P: PromptProvider>(
190
     fn authenticate_via_session_api<P: PromptProvider>(
163
         &self,
191
         &self,
164
-        _username: &str,
192
+        username_line: &str,
165
-        _cookie: &str,
193
+        cookie_line: &str,
166
-        _prompts: &mut P,
194
+        prompts: &mut P,
167
     ) -> Result<HelperOutcome> {
195
     ) -> 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
+        )
169
     }
208
     }
170
 
209
 
171
     fn authenticate_auto_transport<P: PromptProvider>(
210
     fn authenticate_auto_transport<P: PromptProvider>(
@@ -612,6 +651,23 @@ fn is_no_initial_helper_response_error(err: &anyhow::Error) -> bool {
612
     err.downcast_ref::<NoInitialHelperResponseError>().is_some()
651
     err.downcast_ref::<NoInitialHelperResponseError>().is_some()
613
 }
652
 }
614
 
653
 
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
+
615
 fn write_line(stream: &mut impl Write, value: &str) -> Result<()> {
671
 fn write_line(stream: &mut impl Write, value: &str) -> Result<()> {
616
     stream.write_all(value.as_bytes())?;
672
     stream.write_all(value.as_bytes())?;
617
     stream.write_all(b"\n")?;
673
     stream.write_all(b"\n")?;
@@ -1003,18 +1059,67 @@ mod tests {
1003
     }
1059
     }
1004
 
1060
 
1005
     #[test]
1061
     #[test]
1006
-    fn session_backend_reports_not_implemented() {
1062
+    fn session_backend_reports_unavailable_without_setuid_helper() {
1007
         let client = HelperSocketClient::new(temp_socket_path());
1063
         let client = HelperSocketClient::new(temp_socket_path());
1008
         let mut prompts = FakePrompt::default();
1064
         let mut prompts = FakePrompt::default();
1009
         let err = client
1065
         let err = client
1010
             .authenticate_via_session_api("alice", "cookie-session", &mut prompts)
1066
             .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");
1012
         assert!(
1068
         assert!(
1013
             err.to_string()
1069
             err.to_string()
1014
-                .contains("session-api helper conversation backend is not yet implemented")
1070
+                .contains("session-api helper conversation backend unavailable")
1015
         );
1071
         );
1016
     }
1072
     }
1017
 
1073
 
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
+
1018
     #[test]
1123
     #[test]
1019
     fn helper_client_drives_prompt_round_trip() {
1124
     fn helper_client_drives_prompt_round_trip() {
1020
         let socket_path = temp_socket_path();
1125
         let socket_path = temp_socket_path();