From 88a99211f31e07c6fb1ff6a3fadb73d2aa421569 Mon Sep 17 00:00:00 2001 From: 21pages Date: Thu, 19 Sep 2024 18:47:37 +0800 Subject: [PATCH] replace pkexec with gtk sudo (#9383) * Fix https://github.com/rustdesk/rustdesk/issues/9286, replace pkexec with gtk sudo. Tested on gnome (ubuntu 22.04, debian 13), xfce (manjaro, suse), kde (kubuntu 23), lxqt (lubuntu 22), Cinnamon (mint 21.3), Mate (mint 21.2) * Fix incorrect config of the main window opened by the tray, replace xdg-open with run_me, replace with dbus + run_me * Fix `check_if_stop_service`, it causes the problem fixed in https://github.com/rustdesk/rustdesk/pull/8414, now revert that fix and fix itself. Signed-off-by: 21pages --- Cargo.lock | 23 +- Cargo.toml | 3 + build.py | 4 - res/com.rustdesk.RustDesk.policy | 23 - src/core_main.rs | 7 + src/lang/ar.rs | 2 + src/lang/be.rs | 2 + src/lang/bg.rs | 2 + src/lang/ca.rs | 2 + src/lang/cn.rs | 2 + src/lang/cs.rs | 2 + src/lang/da.rs | 2 + src/lang/de.rs | 2 + src/lang/el.rs | 2 + src/lang/eo.rs | 2 + src/lang/es.rs | 2 + src/lang/et.rs | 2 + src/lang/eu.rs | 2 + src/lang/fa.rs | 2 + src/lang/fr.rs | 2 + src/lang/he.rs | 2 + src/lang/hr.rs | 2 + src/lang/hu.rs | 2 + src/lang/id.rs | 2 + src/lang/it.rs | 2 + src/lang/ja.rs | 2 + src/lang/ko.rs | 2 + src/lang/kz.rs | 2 + src/lang/lt.rs | 2 + src/lang/lv.rs | 2 + src/lang/nb.rs | 2 + src/lang/nl.rs | 2 + src/lang/pl.rs | 2 + src/lang/pt_PT.rs | 2 + src/lang/ptbr.rs | 2 + src/lang/ro.rs | 2 + src/lang/ru.rs | 2 + src/lang/sk.rs | 2 + src/lang/sl.rs | 2 + src/lang/sq.rs | 2 + src/lang/sr.rs | 2 + src/lang/sv.rs | 2 + src/lang/template.rs | 2 + src/lang/th.rs | 2 + src/lang/tr.rs | 2 + src/lang/tw.rs | 2 + src/lang/uk.rs | 2 + src/lang/vn.rs | 2 + src/platform/gtk_sudo.rs | 774 +++++++++++++++++++++++++++++++ src/platform/linux.rs | 58 +-- src/platform/mod.rs | 3 + src/tray.rs | 9 +- 52 files changed, 916 insertions(+), 74 deletions(-) delete mode 100644 res/com.rustdesk.RustDesk.policy create mode 100644 src/platform/gtk_sudo.rs diff --git a/Cargo.lock b/Cargo.lock index 3e793ddb7..486b7a6f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -860,6 +860,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chrono" version = "0.4.38" @@ -3967,11 +3973,23 @@ checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ "bitflags 2.6.0", "cfg-if 1.0.0", - "cfg_aliases", + "cfg_aliases 0.1.1", "libc", "memoffset 0.9.1", ] +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags 2.6.0", + "cfg-if 1.0.0", + "cfg_aliases 0.2.1", + "libc", +] + [[package]] name = "nom" version = "7.1.3" @@ -5494,6 +5512,7 @@ dependencies = [ "flutter_rust_bridge", "fon", "fruitbasket", + "gtk", "hbb_common", "hex", "hound", @@ -5508,6 +5527,7 @@ dependencies = [ "libpulse-simple-binding", "mac_address", "magnum-opus", + "nix 0.29.0", "num_cpus", "objc", "objc_id", @@ -5539,6 +5559,7 @@ dependencies = [ "system_shutdown", "tao", "tauri-winrt-notification", + "termios", "totp-rs", "tray-icon", "url", diff --git a/Cargo.toml b/Cargo.toml index 28c2c363d..c71b2918b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -161,6 +161,9 @@ x11-clipboard = {git="https://github.com/clslaid/x11-clipboard", branch = "feat/ x11rb = {version = "0.12", features = ["all-extensions"], optional = true} percent-encoding = {version = "2.3", optional = true} once_cell = {version = "1.18", optional = true} +nix = { version = "0.29", features = ["term", "process"]} +gtk = "0.18" +termios = "0.3" [target.'cfg(target_os = "android")'.dependencies] android_logger = "0.13" diff --git a/build.py b/build.py index be13207ff..3ad206ab1 100755 --- a/build.py +++ b/build.py @@ -331,8 +331,6 @@ def build_flutter_deb(version, features): 'cp ../res/rustdesk.desktop tmpdeb/usr/share/applications/rustdesk.desktop') system2( 'cp ../res/rustdesk-link.desktop tmpdeb/usr/share/applications/rustdesk-link.desktop') - system2( - 'cp ../res/com.rustdesk.RustDesk.policy tmpdeb/usr/share/polkit-1/actions/') system2( 'cp ../res/startwm.sh tmpdeb/etc/rustdesk/') system2( @@ -376,8 +374,6 @@ def build_deb_from_folder(version, binary_folder): 'cp ../res/rustdesk.desktop tmpdeb/usr/share/applications/rustdesk.desktop') system2( 'cp ../res/rustdesk-link.desktop tmpdeb/usr/share/applications/rustdesk-link.desktop') - system2( - 'cp ../res/com.rustdesk.RustDesk.policy tmpdeb/usr/share/polkit-1/actions/') system2( "echo \"#!/bin/sh\" >> tmpdeb/usr/share/rustdesk/files/polkit && chmod a+x tmpdeb/usr/share/rustdesk/files/polkit") diff --git a/res/com.rustdesk.RustDesk.policy b/res/com.rustdesk.RustDesk.policy deleted file mode 100644 index 55f13629b..000000000 --- a/res/com.rustdesk.RustDesk.policy +++ /dev/null @@ -1,23 +0,0 @@ - - - - RustDesk - https://rustdesk.com/ - rustdesk - - Change RustDesk options - Authentication is required to change RustDesk options - 要更改RustDesk选项, 需要您先通过身份验证 - 要變更RustDesk選項, 需要您先通過身份驗證 - Authentifizierung zum Ändern der RustDesk-Optionen - /usr/share/rustdesk/files/polkit - true - - auth_admin - auth_admin - auth_admin - - - diff --git a/src/core_main.rs b/src/core_main.rs index 5d137516e..23d7706d4 100644 --- a/src/core_main.rs +++ b/src/core_main.rs @@ -482,6 +482,13 @@ pub fn core_main() -> Option> { crate::flutter::connection_manager::start_cm_no_ui(); } return None; + } else if args[0] == "-gtk-sudo" { + // rustdesk service kill `rustdesk --` processes + #[cfg(target_os = "linux")] + if args.len() > 2 { + crate::platform::gtk_sudo::exec(); + } + return None; } else { #[cfg(all(feature = "flutter", feature = "plugin_framework"))] #[cfg(not(any(target_os = "android", target_os = "ios")))] diff --git a/src/lang/ar.rs b/src/lang/ar.rs index a157d3d9d..be1a6b767 100644 --- a/src/lang/ar.rs +++ b/src/lang/ar.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", ""), ("Invalid file name", ""), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/be.rs b/src/lang/be.rs index 4fbfff131..fb8444bec 100644 --- a/src/lang/be.rs +++ b/src/lang/be.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", ""), ("Invalid file name", ""), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/bg.rs b/src/lang/bg.rs index 2992911a8..b683a5293 100644 --- a/src/lang/bg.rs +++ b/src/lang/bg.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", "Възобновяване"), ("Invalid file name", "Невалидно име за файл"), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ca.rs b/src/lang/ca.rs index 260b0b4c6..a52da9735 100644 --- a/src/lang/ca.rs +++ b/src/lang/ca.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", ""), ("Invalid file name", ""), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/cn.rs b/src/lang/cn.rs index dc986aaca..887a72971 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", "继续"), ("Invalid file name", "无效文件名"), ("one-way-file-transfer-tip", "被控端启用了单项文件传输"), + ("Authentication Required", "需要身份验证"), + ("Authenticate", "认证"), ].iter().cloned().collect(); } diff --git a/src/lang/cs.rs b/src/lang/cs.rs index c3a1b9758..67588bfb8 100644 --- a/src/lang/cs.rs +++ b/src/lang/cs.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", "Pokračovat"), ("Invalid file name", "Nesprávný název souboru"), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/da.rs b/src/lang/da.rs index be02a7360..aea1514ae 100644 --- a/src/lang/da.rs +++ b/src/lang/da.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", "Fortsæt"), ("Invalid file name", "Ugyldigt filnavn"), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/de.rs b/src/lang/de.rs index 3069ebe3c..38720f537 100644 --- a/src/lang/de.rs +++ b/src/lang/de.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", "Fortsetzen"), ("Invalid file name", "Ungültiger Dateiname"), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/el.rs b/src/lang/el.rs index e05aa9fb8..fecff2894 100644 --- a/src/lang/el.rs +++ b/src/lang/el.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", ""), ("Invalid file name", ""), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/eo.rs b/src/lang/eo.rs index d8cb8ec34..eb512922e 100644 --- a/src/lang/eo.rs +++ b/src/lang/eo.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", ""), ("Invalid file name", ""), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index c087b287c..e42a5abed 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", "Continuar"), ("Invalid file name", "Nombre de archivo no válido"), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/et.rs b/src/lang/et.rs index 24901265f..2443faae9 100644 --- a/src/lang/et.rs +++ b/src/lang/et.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", ""), ("Invalid file name", ""), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/eu.rs b/src/lang/eu.rs index 9213c9689..7c953ebe4 100644 --- a/src/lang/eu.rs +++ b/src/lang/eu.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", ""), ("Invalid file name", ""), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fa.rs b/src/lang/fa.rs index 47988a95d..a97566d99 100644 --- a/src/lang/fa.rs +++ b/src/lang/fa.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", ""), ("Invalid file name", ""), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/fr.rs b/src/lang/fr.rs index 688bcf25f..6a9ca8d8d 100644 --- a/src/lang/fr.rs +++ b/src/lang/fr.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", ""), ("Invalid file name", ""), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/he.rs b/src/lang/he.rs index 7fc4758e1..5a42c4257 100644 --- a/src/lang/he.rs +++ b/src/lang/he.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", ""), ("Invalid file name", ""), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hr.rs b/src/lang/hr.rs index 4671a0d47..9a2b8e3a1 100644 --- a/src/lang/hr.rs +++ b/src/lang/hr.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", ""), ("Invalid file name", ""), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/hu.rs b/src/lang/hu.rs index 2ceb6bce4..fb748b6b8 100644 --- a/src/lang/hu.rs +++ b/src/lang/hu.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", ""), ("Invalid file name", ""), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/id.rs b/src/lang/id.rs index 168c78117..9ed642459 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", ""), ("Invalid file name", ""), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/it.rs b/src/lang/it.rs index 08c00ee1f..613dcea94 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", "Riprendi"), ("Invalid file name", "Nome file non valido"), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ja.rs b/src/lang/ja.rs index e58ff25f7..a63687c68 100644 --- a/src/lang/ja.rs +++ b/src/lang/ja.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", "再開"), ("Invalid file name", "無効なファイル名"), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ko.rs b/src/lang/ko.rs index daf85e632..417073d05 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", ""), ("Invalid file name", ""), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/kz.rs b/src/lang/kz.rs index 85829e84a..a23d34448 100644 --- a/src/lang/kz.rs +++ b/src/lang/kz.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", ""), ("Invalid file name", ""), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lt.rs b/src/lang/lt.rs index 7387d34e3..b818cb7e7 100644 --- a/src/lang/lt.rs +++ b/src/lang/lt.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", ""), ("Invalid file name", ""), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/lv.rs b/src/lang/lv.rs index 0d8b19387..a89590bf4 100644 --- a/src/lang/lv.rs +++ b/src/lang/lv.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", "Atsākt"), ("Invalid file name", "Nederīgs faila nosaukums"), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nb.rs b/src/lang/nb.rs index f3c4ed410..eb2e21b68 100644 --- a/src/lang/nb.rs +++ b/src/lang/nb.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", ""), ("Invalid file name", ""), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/nl.rs b/src/lang/nl.rs index c6d7bcbd3..5b2eee79e 100644 --- a/src/lang/nl.rs +++ b/src/lang/nl.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", "Hervatten"), ("Invalid file name", "Ongeldige bestandsnaam"), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pl.rs b/src/lang/pl.rs index 59e4cbad2..99bf57084 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", "Wznów"), ("Invalid file name", "Nieprawidłowa nazwa pliku"), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/pt_PT.rs b/src/lang/pt_PT.rs index d172cb4f9..534efe12e 100644 --- a/src/lang/pt_PT.rs +++ b/src/lang/pt_PT.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", ""), ("Invalid file name", ""), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ptbr.rs b/src/lang/ptbr.rs index 6a5300028..4396dd40d 100644 --- a/src/lang/ptbr.rs +++ b/src/lang/ptbr.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", "Continuar"), ("Invalid file name", "Nome de arquivo inválido"), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ro.rs b/src/lang/ro.rs index 7b3ba2b28..ea1d98880 100644 --- a/src/lang/ro.rs +++ b/src/lang/ro.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", ""), ("Invalid file name", ""), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/ru.rs b/src/lang/ru.rs index 59a499595..f7db94bd2 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", "Продолжить"), ("Invalid file name", "Неверное имя файла"), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sk.rs b/src/lang/sk.rs index 7b4678423..e7b17c6ae 100644 --- a/src/lang/sk.rs +++ b/src/lang/sk.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", "Obnoviť"), ("Invalid file name", "Nesprávny názov súboru"), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sl.rs b/src/lang/sl.rs index 637859cd4..acbaf2ea9 100755 --- a/src/lang/sl.rs +++ b/src/lang/sl.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", ""), ("Invalid file name", ""), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sq.rs b/src/lang/sq.rs index fe9a30c0c..aca5b6a7a 100644 --- a/src/lang/sq.rs +++ b/src/lang/sq.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", ""), ("Invalid file name", ""), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sr.rs b/src/lang/sr.rs index 906dc0d3f..c12555c97 100644 --- a/src/lang/sr.rs +++ b/src/lang/sr.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", ""), ("Invalid file name", ""), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/sv.rs b/src/lang/sv.rs index e877aca0c..837f702f8 100644 --- a/src/lang/sv.rs +++ b/src/lang/sv.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", ""), ("Invalid file name", ""), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/template.rs b/src/lang/template.rs index 7b08bf3a3..43fbbcfa5 100644 --- a/src/lang/template.rs +++ b/src/lang/template.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", ""), ("Invalid file name", ""), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/th.rs b/src/lang/th.rs index 1b818f479..1c5b09333 100644 --- a/src/lang/th.rs +++ b/src/lang/th.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", ""), ("Invalid file name", ""), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tr.rs b/src/lang/tr.rs index 26085f539..10fa46880 100644 --- a/src/lang/tr.rs +++ b/src/lang/tr.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", ""), ("Invalid file name", ""), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/tw.rs b/src/lang/tw.rs index 690792dbb..812d5f861 100644 --- a/src/lang/tw.rs +++ b/src/lang/tw.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", "繼續"), ("Invalid file name", "無效文件名"), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/uk.rs b/src/lang/uk.rs index ccc73b7b0..aa34887be 100644 --- a/src/lang/uk.rs +++ b/src/lang/uk.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", ""), ("Invalid file name", ""), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/lang/vn.rs b/src/lang/vn.rs index 408060a09..4f7bcf7d1 100644 --- a/src/lang/vn.rs +++ b/src/lang/vn.rs @@ -645,5 +645,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("Resume", ""), ("Invalid file name", ""), ("one-way-file-transfer-tip", ""), + ("Authentication Required", ""), + ("Authenticate", ""), ].iter().cloned().collect(); } diff --git a/src/platform/gtk_sudo.rs b/src/platform/gtk_sudo.rs new file mode 100644 index 000000000..a3727134e --- /dev/null +++ b/src/platform/gtk_sudo.rs @@ -0,0 +1,774 @@ +// https://github.com/aarnt/qt-sudo +// Sometimes reboot is needed to refresh sudoers. + +use crate::lang::translate; +use gtk::{glib, prelude::*}; +use hbb_common::{ + anyhow::{bail, Error}, + log, ResultType, +}; +use nix::{ + libc::{fcntl, kill}, + pty::{forkpty, ForkptyResult}, + sys::{ + signal::Signal, + wait::{waitpid, WaitPidFlag}, + }, + unistd::{execvp, setsid, Pid}, +}; +use std::{ + ffi::CString, + fs::File, + io::{Read, Write}, + os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd}, + sync::{ + mpsc::{channel, Receiver, Sender}, + Arc, Mutex, + }, +}; + +const EXIT_CODE: i32 = -1; + +enum Message { + PasswordPrompt(String), + Password((String, String)), + ErrorDialog(String), + Cancel, + Exit(i32), +} + +pub fn run(cmds: Vec<&str>) -> ResultType<()> { + // rustdesk service kill `rustdesk --` processes + let second_arg = std::env::args().nth(1).unwrap_or_default(); + let cmd_mode = + second_arg.starts_with("--") && second_arg != "--tray" && second_arg != "--no-server"; + let mod_arg = if cmd_mode { "cmd" } else { "gui" }; + let mut args = vec!["-gtk-sudo", mod_arg]; + args.append(&mut cmds.clone()); + let mut child = crate::run_me(args)?; + let exit_status = child.wait()?; + if exit_status.success() { + Ok(()) + } else { + bail!("child exited with status: {:?}", exit_status); + } +} + +pub fn exec() { + let mut args = vec![]; + for arg in std::env::args().skip(3) { + args.push(arg); + } + let cmd_mode = std::env::args().nth(2) == Some("cmd".to_string()); + if cmd_mode { + cmd(args); + } else { + ui(args); + } +} + +fn cmd(args: Vec) { + match unsafe { forkpty(None, None) } { + Ok(forkpty_result) => match forkpty_result { + ForkptyResult::Parent { child, master } => { + if let Err(e) = cmd_parent(child, master) { + log::error!("Parent error: {:?}", e); + kill_child(child); + std::process::exit(EXIT_CODE); + } + } + ForkptyResult::Child => { + if let Err(e) = child(None, args) { + log::error!("Child error: {:?}", e); + std::process::exit(EXIT_CODE); + } + } + }, + Err(err) => { + log::error!("forkpty error: {:?}", err); + std::process::exit(EXIT_CODE); + } + } +} + +fn ui(args: Vec) { + // https://docs.gtk.org/gtk4/ctor.Application.new.html + // https://docs.gtk.org/gio/type_func.Application.id_is_valid.html + let application = gtk::Application::new(None, Default::default()); + + let (tx_to_ui, rx_to_ui) = channel::(); + let (tx_from_ui, rx_from_ui) = channel::(); + + let rx_to_ui = Arc::new(Mutex::new(rx_to_ui)); + let tx_from_ui = Arc::new(Mutex::new(tx_from_ui)); + + let rx_to_ui_clone = rx_to_ui.clone(); + let tx_from_ui_clone = tx_from_ui.clone(); + + let username = Arc::new(Mutex::new(crate::platform::get_active_username())); + let username_clone = username.clone(); + let first_prompt = Arc::new(Mutex::new(true)); + + application.connect_activate(glib::clone!(@weak application =>move |_| { + let rx_to_ui = rx_to_ui_clone.clone(); + let tx_from_ui = tx_from_ui_clone.clone(); + let last_password = Arc::new(Mutex::new(String::new())); + let username = username_clone.clone(); + let first_prompt = first_prompt.clone(); + + glib::timeout_add_local(std::time::Duration::from_millis(50), move || { + if let Ok(msg) = rx_to_ui.lock().unwrap().try_recv() { + match msg { + Message::PasswordPrompt(err_msg) => { + let last_pwd = last_password.lock().unwrap().clone(); + let username = username.lock().unwrap().clone(); + let first = first_prompt.lock().unwrap().clone(); + *first_prompt.lock().unwrap() = false; + if let Some((username, password)) = password_prompt(&username, &last_pwd, &err_msg, first) { + *last_password.lock().unwrap() = password.clone(); + if let Err(e) = tx_from_ui + .lock() + .unwrap() + .send(Message::Password((username, password))) { + error_dialog_and_exit(&format!("Channel error: {e:?}"), EXIT_CODE); + } + } else { + if let Err(e) = tx_from_ui.lock().unwrap().send(Message::Cancel) { + error_dialog_and_exit(&format!("Channel error: {e:?}"), EXIT_CODE); + } + } + } + Message::ErrorDialog(err_msg) => { + error_dialog_and_exit(&err_msg, EXIT_CODE); + } + Message::Exit(code) => { + log::info!("Exit code: {}", code); + std::process::exit(code); + } + _ => {} + } + } + glib::ControlFlow::Continue + }); + })); + + let tx_to_ui_clone = tx_to_ui.clone(); + std::thread::spawn(move || { + let acitve_user = crate::platform::get_active_username(); + let mut initial_password = None; + if acitve_user != "root" { + if let Err(e) = tx_to_ui_clone.send(Message::PasswordPrompt("".to_string())) { + log::error!("Channel error: {e:?}"); + std::process::exit(EXIT_CODE); + } + match rx_from_ui.recv() { + Ok(Message::Password((user, password))) => { + *username.lock().unwrap() = user; + initial_password = Some(password); + } + Ok(Message::Cancel) => { + log::info!("User canceled"); + std::process::exit(EXIT_CODE); + } + _ => { + log::error!("Unexpected message"); + std::process::exit(EXIT_CODE); + } + } + } + let username = username.lock().unwrap().clone(); + let su_user = if username == acitve_user { + None + } else { + Some(username) + }; + match unsafe { forkpty(None, None) } { + Ok(forkpty_result) => match forkpty_result { + ForkptyResult::Parent { child, master } => { + if let Err(e) = ui_parent( + child, + master, + tx_to_ui_clone, + rx_from_ui, + su_user.is_some(), + initial_password, + ) { + log::error!("Parent error: {:?}", e); + kill_child(child); + std::process::exit(EXIT_CODE); + } + } + ForkptyResult::Child => { + if let Err(e) = child(su_user, args) { + log::error!("Child error: {:?}", e); + std::process::exit(EXIT_CODE); + } + } + }, + Err(err) => { + log::error!("forkpty error: {:?}", err); + if let Err(e) = + tx_to_ui.send(Message::ErrorDialog(format!("Forkpty error: {:?}", err))) + { + log::error!("Channel error: {e:?}"); + std::process::exit(EXIT_CODE); + } + } + } + }); + + let _holder = application.hold(); + let args: Vec<&str> = vec![]; + application.run_with_args(&args); + log::debug!("exit from gtk::Application::run_with_args"); + std::process::exit(EXIT_CODE); +} + +fn cmd_parent(child: Pid, master: OwnedFd) -> ResultType<()> { + let raw_fd = master.as_raw_fd(); + if unsafe { fcntl(raw_fd, nix::libc::F_SETFL, nix::libc::O_NONBLOCK) } != 0 { + let errno = std::io::Error::last_os_error(); + bail!("fcntl error: {errno:?}"); + } + let mut file = unsafe { File::from_raw_fd(raw_fd) }; + let mut stdout = std::io::stdout(); + let stdin = std::io::stdin(); + let stdin_fd = stdin.as_raw_fd(); + let old_termios = termios::Termios::from_fd(stdin_fd)?; + turn_off_echo(stdin_fd).ok(); + shutdown_hooks::add_shutdown_hook(turn_on_echo_shutdown_hook); + let (tx, rx) = channel::>(); + std::thread::spawn(move || loop { + let mut line = String::default(); + match stdin.read_line(&mut line) { + Ok(0) => { + kill_child(child); + break; + } + Ok(_) => { + if let Err(e) = tx.send(line.as_bytes().to_vec()) { + log::error!("Channel error: {e:?}"); + kill_child(child); + break; + } + } + Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {} + Err(e) => { + log::info!("Failed to read stdin: {e:?}"); + kill_child(child); + break; + } + }; + }); + loop { + let mut buf = [0; 1024]; + match file.read(&mut buf) { + Ok(0) => { + log::info!("read from child: EOF"); + break; + } + Ok(n) => { + let buf = String::from_utf8_lossy(&buf[..n]).to_string(); + print!("{}", buf); + if let Err(e) = stdout.flush() { + log::error!("flush failed: {e:?}"); + kill_child(child); + break; + } + } + Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { + std::thread::sleep(std::time::Duration::from_millis(50)); + } + Err(e) => { + // Child process is dead + log::info!("Read child error: {:?}", e); + break; + } + } + match rx.try_recv() { + Ok(v) => { + if let Err(e) = file.write_all(&v) { + log::error!("write error: {e:?}"); + kill_child(child); + break; + } + } + Err(e) => match e { + std::sync::mpsc::TryRecvError::Empty => {} + std::sync::mpsc::TryRecvError::Disconnected => { + log::error!("receive error: {e:?}"); + kill_child(child); + break; + } + }, + } + } + + // Wait for child process + let status = waitpid(child, None); + log::info!("waitpid status: {:?}", status); + let mut code = EXIT_CODE; + match status { + Ok(s) => match s { + nix::sys::wait::WaitStatus::Exited(_pid, status) => { + code = status; + } + _ => {} + }, + Err(_) => {} + } + termios::tcsetattr(stdin_fd, termios::TCSANOW, &old_termios).ok(); + std::process::exit(code); +} + +fn ui_parent( + child: Pid, + master: OwnedFd, + tx_to_ui: Sender, + rx_from_ui: Receiver, + is_su: bool, + initial_password: Option, +) -> ResultType<()> { + let mut initial_password = initial_password; + let raw_fd = master.as_raw_fd(); + if unsafe { fcntl(raw_fd, nix::libc::F_SETFL, nix::libc::O_NONBLOCK) } != 0 { + let errno = std::io::Error::last_os_error(); + tx_to_ui.send(Message::ErrorDialog(format!("fcntl error: {errno:?}")))?; + bail!("fcntl error: {errno:?}"); + } + let mut file = unsafe { File::from_raw_fd(raw_fd) }; + + let mut first = initial_password.is_none(); + let mut su_password_sent = false; + let mut saved_output = String::default(); + loop { + let mut buf = [0; 1024]; + match file.read(&mut buf) { + Ok(0) => { + log::info!("read from child: EOF"); + break; + } + Ok(n) => { + saved_output = String::default(); + let buf = String::from_utf8_lossy(&buf[..n]).trim().to_string(); + let last_line = buf.lines().last().unwrap_or(&buf).trim().to_string(); + log::info!("read from child: {}", buf); + + if last_line.starts_with("sudo:") || last_line.starts_with("su:") { + if let Err(e) = tx_to_ui.send(Message::ErrorDialog(last_line)) { + log::error!("Channel error: {e:?}"); + kill_child(child); + } + break; + } else if last_line.ends_with(":") { + match get_echo_turn_off(raw_fd) { + Ok(true) => { + log::debug!("get_echo_turn_off ok"); + if let Some(password) = initial_password.clone() { + let v = format!("{}\n", password); + if let Err(e) = file.write_all(v.as_bytes()) { + let e = format!("Failed to send password: {e:?}"); + if let Err(e) = tx_to_ui.send(Message::ErrorDialog(e)) { + log::error!("Channel error: {e:?}"); + } + kill_child(child); + break; + } + if is_su && !su_password_sent { + su_password_sent = true; + continue; + } + initial_password = None; + continue; + } + // In fact, su mode can only input password once + let err_msg = if first { "" } else { "Sorry, try again." }; + first = false; + if let Err(e) = + tx_to_ui.send(Message::PasswordPrompt(err_msg.to_string())) + { + log::error!("Channel error: {e:?}"); + kill_child(child); + break; + } + match rx_from_ui.recv() { + Ok(Message::Password((_, password))) => { + let v = format!("{}\n", password); + if let Err(e) = file.write_all(v.as_bytes()) { + let e = format!("Failed to send password: {e:?}"); + if let Err(e) = tx_to_ui.send(Message::ErrorDialog(e)) { + log::error!("Channel error: {e:?}"); + } + kill_child(child); + break; + } + } + Ok(Message::Cancel) => { + log::info!("User canceled"); + kill_child(child); + break; + } + _ => { + log::error!("Unexpected message"); + break; + } + } + } + Ok(false) => log::warn!("get_echo_turn_off timeout"), + Err(e) => log::error!("get_echo_turn_off error: {:?}", e), + } + } else { + saved_output = buf.clone(); + if !last_line.is_empty() && initial_password.is_some() { + log::error!("received not empty line: {last_line}, clear initial password"); + initial_password = None; + } + } + } + Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { + std::thread::sleep(std::time::Duration::from_millis(50)); + } + Err(e) => { + // Child process is dead + log::debug!("Read error: {:?}", e); + break; + } + } + } + + // Wait for child process + let status = waitpid(child, None); + log::info!("waitpid status: {:?}", status); + let mut code = EXIT_CODE; + match status { + Ok(s) => match s { + nix::sys::wait::WaitStatus::Exited(_pid, status) => { + code = status; + } + _ => {} + }, + Err(_) => {} + } + + if code != 0 && !saved_output.is_empty() { + if let Err(e) = tx_to_ui.send(Message::ErrorDialog(saved_output.clone())) { + log::error!("Channel error: {e:?}"); + std::process::exit(code); + } + return Ok(()); + } + if let Err(e) = tx_to_ui.send(Message::Exit(code)) { + log::error!("Channel error: {e:?}"); + std::process::exit(code); + } + Ok(()) +} + +fn child(su_user: Option, args: Vec) -> ResultType<()> { + // https://doc.rust-lang.org/std/env/consts/constant.OS.html + let os = std::env::consts::OS; + let bsd = os == "freebsd" || os == "dragonfly" || os == "netbsd" || os == "openbad"; + let mut params = vec!["sudo".to_string()]; + if su_user.is_some() { + params.push("-S".to_string()); + } + params.push("/bin/sh".to_string()); + params.push("-c".to_string()); + + let command = args + .iter() + .map(|s| { + if su_user.is_some() { + s.to_string() + } else { + quote_shell_arg(s, true) + } + }) + .collect::>() + .join(" "); + let mut command = if bsd { + let lc = match std::env::var("LC_ALL") { + Ok(lc_all) => { + if lc_all.contains('\'') { + eprintln!( + "sudo: Detected attempt to inject privileged command via LC_ALL env({lc_all}). Exiting!\n", + ); + std::process::exit(EXIT_CODE); + } + format!("LC_ALL='{lc_all}' ") + } + Err(_) => { + format!("unset LC_ALL;") + } + }; + format!("{}exec {}", lc, command) + } else { + command.to_string() + }; + if su_user.is_some() { + command = format!("'{}'", quote_shell_arg(&command, false)); + } + params.push(command); + std::env::set_var("LC_ALL", "C.UTF-8"); + + if let Some(user) = &su_user { + let su_subcommand = params + .iter() + .map(|p| p.to_string()) + .collect::>() + .join(" "); + params = vec![ + "su".to_string(), + "-".to_string(), + user.to_string(), + "-c".to_string(), + su_subcommand, + ]; + } + + // allow failure here + let _ = setsid(); + let mut cparams = vec![]; + for param in ¶ms { + cparams.push(CString::new(param.as_str())?); + } + let su_or_sudo = if su_user.is_some() { "su" } else { "sudo" }; + let res = execvp(CString::new(su_or_sudo)?.as_c_str(), &cparams); + eprintln!("sudo: execvp error: {:?}", res); + std::process::exit(EXIT_CODE); +} + +fn get_echo_turn_off(fd: RawFd) -> Result { + let tios = termios::Termios::from_fd(fd)?; + for _ in 0..10 { + if tios.c_lflag & termios::ECHO == 0 { + return Ok(true); + } + std::thread::sleep(std::time::Duration::from_millis(10)); + } + Ok(false) +} + +fn turn_off_echo(fd: RawFd) -> Result<(), Error> { + use termios::*; + let mut termios = Termios::from_fd(fd)?; + // termios.c_lflag &= !(ECHO | ECHONL | ICANON | IEXTEN); + termios.c_lflag &= !ECHO; + tcsetattr(fd, TCSANOW, &termios)?; + Ok(()) +} + +pub extern "C" fn turn_on_echo_shutdown_hook() { + let fd = std::io::stdin().as_raw_fd(); + if let Ok(mut termios) = termios::Termios::from_fd(fd) { + termios.c_lflag |= termios::ECHO; + termios::tcsetattr(fd, termios::TCSANOW, &termios).ok(); + } +} + +fn kill_child(child: Pid) { + unsafe { kill(child.as_raw(), Signal::SIGINT as _) }; + let mut res = 0; + + for _ in 0..10 { + match waitpid(child, Some(WaitPidFlag::WNOHANG)) { + Ok(_) => { + res = 1; + break; + } + Err(_) => (), + } + std::thread::sleep(std::time::Duration::from_millis(100)); + } + + if res == 0 { + log::info!("Force killing child process"); + unsafe { kill(child.as_raw(), Signal::SIGKILL as _) }; + } +} + +fn password_prompt( + username: &str, + last_password: &str, + err: &str, + show_edit: bool, +) -> Option<(String, String)> { + let dialog = gtk::Dialog::builder() + .title(crate::get_app_name()) + .modal(true) + .build(); + // https://docs.gtk.org/gtk4/method.Dialog.set_default_response.html + dialog.set_default_response(gtk::ResponseType::Ok); + let content_area = dialog.content_area(); + + let label = gtk::Label::builder() + .label(translate("Authentication Required".to_string())) + .margin_top(10) + .build(); + content_area.add(&label); + + let image = gtk::Image::from_icon_name(Some("avatar-default-symbolic"), gtk::IconSize::Dialog); + image.set_margin_top(10); + content_area.add(&image); + + let user_label = gtk::Label::new(Some(username)); + let edit_button = gtk::Button::new(); + edit_button.set_relief(gtk::ReliefStyle::None); + let edit_icon = + gtk::Image::from_icon_name(Some("document-edit-symbolic"), gtk::IconSize::Button.into()); + edit_button.set_image(Some(&edit_icon)); + edit_button.set_can_focus(false); + let user_entry = gtk::Entry::new(); + user_entry.set_alignment(0.5); + user_entry.set_width_request(100); + let user_box = gtk::Box::new(gtk::Orientation::Horizontal, 5); + user_box.add(&user_label); + user_box.add(&edit_button); + user_box.add(&user_entry); + user_box.set_halign(gtk::Align::Center); + user_box.set_valign(gtk::Align::Center); + content_area.add(&user_box); + + edit_button.connect_clicked( + glib::clone!(@weak user_label, @weak edit_button, @weak user_entry=> move |_| { + let username = user_label.text().to_string(); + user_entry.set_text(&username); + user_label.hide(); + edit_button.hide(); + user_entry.show(); + user_entry.grab_focus(); + }), + ); + + let password_input = gtk::Entry::builder() + .visibility(false) + .input_purpose(gtk::InputPurpose::Password) + .placeholder_text(translate("Password".to_string())) + .margin_top(20) + .margin_start(30) + .margin_end(30) + .activates_default(true) + .text(last_password) + .build(); + password_input.set_alignment(0.5); + // https://docs.gtk.org/gtk3/signal.Entry.activate.html + password_input.connect_activate(glib::clone!(@weak dialog => move |_| { + dialog.response(gtk::ResponseType::Ok); + })); + content_area.add(&password_input); + + user_entry.connect_focus_out_event( + glib::clone!(@weak user_label, @weak edit_button, @weak user_entry, @weak password_input => @default-return glib::Propagation::Proceed, move |_, _| { + let username = user_entry.text().to_string(); + user_label.set_text(&username); + user_entry.hide(); + user_label.show(); + edit_button.show(); + glib::Propagation::Proceed + }), + ); + user_entry.connect_activate( + glib::clone!(@weak user_label, @weak edit_button, @weak user_entry, @weak password_input => move |_| { + let username = user_entry.text().to_string(); + user_label.set_text(&username); + user_entry.hide(); + user_label.show(); + edit_button.show(); + password_input.grab_focus(); + }), + ); + + if !err.is_empty() { + let err_label = gtk::Label::new(None); + err_label.set_markup(&format!( + "{}", + err + )); + err_label.set_selectable(true); + content_area.add(&err_label); + } + + let cancel_button = gtk::Button::builder() + .label(translate("Cancel".to_string())) + .expand(true) + .build(); + cancel_button.connect_clicked(glib::clone!(@weak dialog => move |_| { + dialog.response(gtk::ResponseType::Cancel); + })); + let authenticate_button = gtk::Button::builder() + .label(translate("Authenticate".to_string())) + .expand(true) + .build(); + authenticate_button.connect_clicked(glib::clone!(@weak dialog => move |_| { + dialog.response(gtk::ResponseType::Ok); + })); + let button_box = gtk::Box::builder() + .orientation(gtk::Orientation::Horizontal) + .expand(true) + .homogeneous(true) + .spacing(10) + .margin_top(10) + .build(); + button_box.add(&cancel_button); + button_box.add(&authenticate_button); + content_area.add(&button_box); + + content_area.set_spacing(10); + content_area.set_border_width(10); + + dialog.set_width_request(400); + dialog.show_all(); + dialog.set_position(gtk::WindowPosition::Center); + dialog.set_keep_above(true); + password_input.grab_focus(); + user_entry.hide(); + if !show_edit { + edit_button.hide(); + } + dialog.check_resize(); + let response = dialog.run(); + dialog.hide(); + + if response == gtk::ResponseType::Ok { + let username = if user_entry.get_visible() { + user_entry.text().to_string() + } else { + user_label.text().to_string() + }; + Some((username, password_input.text().to_string())) + } else { + None + } +} + +fn error_dialog_and_exit(err_msg: &str, exit_code: i32) { + log::error!("Error dialog: {err_msg}, exit code: {exit_code}"); + let dialog = gtk::MessageDialog::builder() + .message_type(gtk::MessageType::Error) + .title(crate::get_app_name()) + .text("Error") + .secondary_text(err_msg) + .modal(true) + .buttons(gtk::ButtonsType::Ok) + .build(); + dialog.set_position(gtk::WindowPosition::Center); + dialog.set_keep_above(true); + dialog.run(); + dialog.close(); + std::process::exit(exit_code); +} + +fn quote_shell_arg(arg: &str, add_splash_if_match: bool) -> String { + let mut rv = arg.to_string(); + let re = hbb_common::regex::Regex::new("(\\s|[][!\"#$&'()*,;<=>?\\^`{}|~])"); + let Ok(re) = re else { + return rv; + }; + if re.is_match(arg) { + rv = rv.replace("'", "'\\''"); + if add_splash_if_match { + rv = format!("'{}'", rv); + } + } + rv +} diff --git a/src/platform/linux.rs b/src/platform/linux.rs index 4bb666fb9..9c5494230 100644 --- a/src/platform/linux.rs +++ b/src/platform/linux.rs @@ -1,4 +1,4 @@ -use super::{CursorData, ResultType}; +use super::{gtk_sudo, CursorData, ResultType}; use desktop::Desktop; use hbb_common::config::keys::OPTION_ALLOW_LINUX_HEADLESS; pub use hbb_common::platform::linux::*; @@ -15,8 +15,6 @@ use hbb_common::{ use std::{ cell::RefCell, ffi::OsStr, - fs::File, - io::{BufRead, BufReader, Write}, path::{Path, PathBuf}, process::{Child, Command}, string::String, @@ -766,30 +764,18 @@ pub fn quit_gui() { unsafe { gtk_main_quit() }; } +/* pub fn exec_privileged(args: &[&str]) -> ResultType { Ok(Command::new("pkexec").args(args).spawn()?) } +*/ pub fn check_super_user_permission() -> ResultType { - let file = format!( - "/usr/share/{}/files/polkit", - crate::get_app_name().to_lowercase() - ); - let arg; - if Path::new(&file).is_file() { - arg = file.as_str(); - } else { - arg = "echo"; - } - // https://github.com/rustdesk/rustdesk/issues/2756 - if let Ok(status) = Command::new("pkexec").arg(arg).status() { - // https://github.com/rustdesk/rustdesk/issues/5205#issuecomment-1658059657s - Ok(status.code() != Some(126) && status.code() != Some(127)) - } else { - Ok(true) - } + gtk_sudo::run(vec!["echo"])?; + Ok(true) } +/* pub fn elevate(args: Vec<&str>) -> ResultType { let cmd = std::env::current_exe()?; match cmd.to_str() { @@ -824,6 +810,7 @@ pub fn elevate(args: Vec<&str>) -> ResultType { } } } +*/ type GtkSettingsPtr = *mut c_void; type GObjectPtr = *mut c_void; @@ -1324,21 +1311,8 @@ fn has_cmd(cmd: &str) -> bool { .unwrap_or_default() } -pub fn run_cmds_pkexec(cmds: &str) -> bool { - const DONE: &str = "RUN_CMDS_PKEXEC_DONE"; - if let Ok(output) = std::process::Command::new("pkexec") - .arg("sh") - .arg("-c") - .arg(&format!("{cmds} echo {DONE}")) - .output() - { - let out = String::from_utf8_lossy(&output.stdout); - log::debug!("cmds: {cmds}"); - log::debug!("output: {out}"); - out.contains(DONE) - } else { - false - } +pub fn run_cmds_privileged(cmds: &str) -> bool { + crate::platform::gtk_sudo::run(vec![cmds]).is_ok() } pub fn run_me_with(secs: u32) { @@ -1367,13 +1341,15 @@ fn switch_service(stop: bool) -> String { pub fn uninstall_service(show_new_window: bool, _: bool) -> bool { if !has_cmd("systemctl") { + // Failed when installed + flutter run + started by `show_new_window`. return false; } log::info!("Uninstalling service..."); let cp = switch_service(true); let app_name = crate::get_app_name().to_lowercase(); - if !run_cmds_pkexec(&format!( - "systemctl disable {app_name}; systemctl stop {app_name}; {cp}" + // systemctl kill rustdesk --tray, execute cp first + if !run_cmds_privileged(&format!( + "{cp} systemctl disable {app_name}; systemctl stop {app_name};" )) { Config::set_option("stop-service".into(), "".into()); return true; @@ -1393,8 +1369,8 @@ pub fn install_service() -> bool { log::info!("Installing service..."); let cp = switch_service(false); let app_name = crate::get_app_name().to_lowercase(); - if !run_cmds_pkexec(&format!( - "{cp} systemctl enable {app_name}; systemctl stop {app_name}; systemctl start {app_name};" + if !run_cmds_privileged(&format!( + "{cp} systemctl enable {app_name}; systemctl start {app_name};" )) { Config::set_option("stop-service".into(), "Y".into()); } @@ -1404,9 +1380,9 @@ pub fn install_service() -> bool { fn check_if_stop_service() { if Config::get_option("stop-service".into()) == "Y" { let app_name = crate::get_app_name().to_lowercase(); - allow_err!(run_cmds( + allow_err!(run_cmds(&format!( "systemctl disable {app_name}; systemctl stop {app_name}" - )); + ))); } } diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 169bdb199..d0ddd09bf 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -23,6 +23,9 @@ pub mod linux; #[cfg(target_os = "linux")] pub mod linux_desktop_manager; +#[cfg(target_os = "linux")] +pub mod gtk_sudo; + #[cfg(not(any(target_os = "android", target_os = "ios")))] use hbb_common::{message_proto::CursorData, ResultType}; use std::sync::{Arc, Mutex}; diff --git a/src/tray.rs b/src/tray.rs index 74c18bf7b..3a3ae92f3 100644 --- a/src/tray.rs +++ b/src/tray.rs @@ -98,12 +98,11 @@ fn make_tray() -> hbb_common::ResultType<()> { crate::run_me::<&str>(vec![]).ok(); } #[cfg(target_os = "linux")] - if !std::process::Command::new("xdg-open") - .arg(&crate::get_uri_prefix()) - .spawn() - .is_ok() { - crate::run_me::<&str>(vec![]).ok(); + // Do not use "xdg-open", it won't read config + if crate::dbus::invoke_new_connection(crate::get_uri_prefix()).is_err() { + crate::run_me::<&str>(vec![]).ok(); + } } };