From bf200dbf8ec7c861c02cf394b6c6da0c58b8f95e Mon Sep 17 00:00:00 2001
From: Joerg Schirottke <master@kanotix.com>
Date: Thu, 27 Aug 2026 12:24:32 +0200
Subject: [PATCH 1/3] fix(linux): fall back to su when user cannot use sudo

Assisted-by: DeepSeek V4 Flash Free
---
 src/platform/gtk_sudo.rs | 69 ++++++++++++++++++++++++++++++++--------
 1 file changed, 55 insertions(+), 14 deletions(-)

diff --git a/src/platform/gtk_sudo.rs b/src/platform/gtk_sudo.rs
index 37b541c..a4d9406 100644
--- a/src/platform/gtk_sudo.rs
+++ b/src/platform/gtk_sudo.rs
@@ -39,6 +39,24 @@ enum Message {
     Exit(i32),
 }
 
+// Whether the current user is allowed to run `sudo` (with or without a password).
+// `sudo -n -l` exits non-zero with "a password is required" when the user is permitted
+// to use sudo but a password is needed, and with "not in the sudoers file" (or an
+// exec error) when the user is not permitted to use sudo at all.
+fn can_use_sudo() -> bool {
+    let Ok(output) = std::process::Command::new("sudo")
+        .env("LC_ALL", "C")
+        .args(["-n", "-l"])
+        .output()
+    else {
+        return false;
+    };
+    if output.status.success() {
+        return true;
+    }
+    String::from_utf8_lossy(&output.stderr).contains("a password is required")
+}
+
 pub fn run(cmds: Vec<&str>) -> ResultType<()> {
     // rustdesk service kill `rustdesk --` processes
     let second_arg = std::env::args().nth(1).unwrap_or_default();
@@ -153,9 +171,17 @@ fn ui(args: Vec<String>) {
     let tx_to_ui_clone = tx_to_ui.clone();
     std::thread::spawn(move || {
         let acitve_user = crate::platform::get_active_username();
+        // When the active user is not allowed to run `sudo`, fall back to asking for
+        // the root password and elevating through `su` instead.
+        let force_root = acitve_user != "root" && !can_use_sudo();
+        if force_root {
+            *username.lock().unwrap() = "root".to_string();
+        }
         let mut initial_password = None;
         if acitve_user != "root" {
-            if let Err(e) = tx_to_ui_clone.send(Message::PasswordPrompt(("".to_string(), true))) {
+            // Only the root password is valid when elevating through `su`, so hide the
+            // username editor.
+            if let Err(e) = tx_to_ui_clone.send(Message::PasswordPrompt(("".to_string(), !force_root))) {
                 log::error!("Channel error: {e:?}");
                 std::process::exit(EXIT_CODE);
             }
@@ -175,7 +201,9 @@ fn ui(args: Vec<String>) {
             }
         }
         let username = username.lock().unwrap().clone();
-        let su_user = if username == acitve_user {
+        let su_user = if force_root {
+            Some("root".to_string())
+        } else if username == acitve_user {
             None
         } else {
             Some(username)
@@ -510,18 +538,31 @@ fn child(su_user: Option<String>, args: Vec<String>) -> ResultType<()> {
     std::env::set_var("LC_ALL", "C");
 
     if let Some(user) = &su_user {
-        let su_subcommand = params
-            .iter()
-            .map(|p| p.to_string())
-            .collect::<Vec<String>>()
-            .join(" ");
-        params = vec![
-            "su".to_string(),
-            "-".to_string(),
-            user.to_string(),
-            "-c".to_string(),
-            su_subcommand,
-        ];
+        // When the target is root, `su - -c` already defaults to root and provides root
+        // privileges, so there is no need to wrap the command in `sudo -S` (which may
+        // not be available). params is ["sudo", "-S", "sh", "-c", "<command>"], so the
+        // root subcommand drops the sudo wrapper.
+        let su_subcommand = if user == "root" {
+            params
+                .iter()
+                .skip(2)
+                .map(|p| p.to_string())
+                .collect::<Vec<String>>()
+                .join(" ")
+        } else {
+            params
+                .iter()
+                .map(|p| p.to_string())
+                .collect::<Vec<String>>()
+                .join(" ")
+        };
+        let mut su_cmd = vec!["su".to_string(), "-".to_string()];
+        if user != "root" {
+            su_cmd.push(user.to_string());
+        }
+        su_cmd.push("-c".to_string());
+        su_cmd.push(su_subcommand);
+        params = su_cmd;
     }
 
     // allow failure here
-- 
2.47.3

