gtk_sudo.rs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774
  1. // https://github.com/aarnt/qt-sudo
  2. // Sometimes reboot is needed to refresh sudoers.
  3. use crate::lang::translate;
  4. use gtk::{glib, prelude::*};
  5. use hbb_common::{
  6. anyhow::{bail, Error},
  7. log,
  8. platform::linux::CMD_SH,
  9. ResultType,
  10. };
  11. use nix::{
  12. libc::{fcntl, kill},
  13. pty::{forkpty, ForkptyResult},
  14. sys::{
  15. signal::Signal,
  16. wait::{waitpid, WaitPidFlag},
  17. },
  18. unistd::{execvp, setsid, Pid},
  19. };
  20. use std::{
  21. ffi::CString,
  22. fs::File,
  23. io::{Read, Write},
  24. os::fd::{AsRawFd, FromRawFd, OwnedFd, RawFd},
  25. sync::{
  26. mpsc::{channel, Receiver, Sender},
  27. Arc, Mutex,
  28. },
  29. };
  30. const EXIT_CODE: i32 = -1;
  31. enum Message {
  32. PasswordPrompt((String, bool)),
  33. Password((String, String)),
  34. ErrorDialog(String),
  35. Cancel,
  36. Exit(i32),
  37. }
  38. pub fn run(cmds: Vec<&str>) -> ResultType<()> {
  39. // rustdesk service kill `rustdesk --` processes
  40. let second_arg = std::env::args().nth(1).unwrap_or_default();
  41. let cmd_mode =
  42. second_arg.starts_with("--") && second_arg != "--tray" && second_arg != "--no-server";
  43. let mod_arg = if cmd_mode { "cmd" } else { "gui" };
  44. let mut args = vec!["-gtk-sudo", mod_arg];
  45. args.append(&mut cmds.clone());
  46. let mut child = crate::run_me(args)?;
  47. let exit_status = child.wait()?;
  48. if exit_status.success() {
  49. Ok(())
  50. } else {
  51. bail!("child exited with status: {:?}", exit_status);
  52. }
  53. }
  54. pub fn exec() {
  55. let mut args = vec![];
  56. for arg in std::env::args().skip(3) {
  57. args.push(arg);
  58. }
  59. let cmd_mode = std::env::args().nth(2) == Some("cmd".to_string());
  60. if cmd_mode {
  61. cmd(args);
  62. } else {
  63. ui(args);
  64. }
  65. }
  66. fn cmd(args: Vec<String>) {
  67. match unsafe { forkpty(None, None) } {
  68. Ok(forkpty_result) => match forkpty_result {
  69. ForkptyResult::Parent { child, master } => {
  70. if let Err(e) = cmd_parent(child, master) {
  71. log::error!("Parent error: {:?}", e);
  72. kill_child(child);
  73. std::process::exit(EXIT_CODE);
  74. }
  75. }
  76. ForkptyResult::Child => {
  77. if let Err(e) = child(None, args) {
  78. log::error!("Child error: {:?}", e);
  79. std::process::exit(EXIT_CODE);
  80. }
  81. }
  82. },
  83. Err(err) => {
  84. log::error!("forkpty error: {:?}", err);
  85. std::process::exit(EXIT_CODE);
  86. }
  87. }
  88. }
  89. fn ui(args: Vec<String>) {
  90. // https://docs.gtk.org/gtk4/ctor.Application.new.html
  91. // https://docs.gtk.org/gio/type_func.Application.id_is_valid.html
  92. let application = gtk::Application::new(None, Default::default());
  93. let (tx_to_ui, rx_to_ui) = channel::<Message>();
  94. let (tx_from_ui, rx_from_ui) = channel::<Message>();
  95. let rx_to_ui = Arc::new(Mutex::new(rx_to_ui));
  96. let tx_from_ui = Arc::new(Mutex::new(tx_from_ui));
  97. let rx_to_ui_clone = rx_to_ui.clone();
  98. let tx_from_ui_clone = tx_from_ui.clone();
  99. let username = Arc::new(Mutex::new(crate::platform::get_active_username()));
  100. let username_clone = username.clone();
  101. application.connect_activate(glib::clone!(@weak application =>move |_| {
  102. let rx_to_ui = rx_to_ui_clone.clone();
  103. let tx_from_ui = tx_from_ui_clone.clone();
  104. let last_password = Arc::new(Mutex::new(String::new()));
  105. let username = username_clone.clone();
  106. glib::timeout_add_local(std::time::Duration::from_millis(50), move || {
  107. if let Ok(msg) = rx_to_ui.lock().unwrap().try_recv() {
  108. match msg {
  109. Message::PasswordPrompt((err_msg, show_edit)) => {
  110. let last_pwd = last_password.lock().unwrap().clone();
  111. let username = username.lock().unwrap().clone();
  112. if let Some((username, password)) = password_prompt(&username, &last_pwd, &err_msg, show_edit) {
  113. *last_password.lock().unwrap() = password.clone();
  114. if let Err(e) = tx_from_ui
  115. .lock()
  116. .unwrap()
  117. .send(Message::Password((username, password))) {
  118. error_dialog_and_exit(&format!("Channel error: {e:?}"), EXIT_CODE);
  119. }
  120. } else {
  121. if let Err(e) = tx_from_ui.lock().unwrap().send(Message::Cancel) {
  122. error_dialog_and_exit(&format!("Channel error: {e:?}"), EXIT_CODE);
  123. }
  124. }
  125. }
  126. Message::ErrorDialog(err_msg) => {
  127. error_dialog_and_exit(&err_msg, EXIT_CODE);
  128. }
  129. Message::Exit(code) => {
  130. log::info!("Exit code: {}", code);
  131. std::process::exit(code);
  132. }
  133. _ => {}
  134. }
  135. }
  136. glib::ControlFlow::Continue
  137. });
  138. }));
  139. let tx_to_ui_clone = tx_to_ui.clone();
  140. std::thread::spawn(move || {
  141. let acitve_user = crate::platform::get_active_username();
  142. let mut initial_password = None;
  143. if acitve_user != "root" {
  144. if let Err(e) = tx_to_ui_clone.send(Message::PasswordPrompt(("".to_string(), true))) {
  145. log::error!("Channel error: {e:?}");
  146. std::process::exit(EXIT_CODE);
  147. }
  148. match rx_from_ui.recv() {
  149. Ok(Message::Password((user, password))) => {
  150. *username.lock().unwrap() = user;
  151. initial_password = Some(password);
  152. }
  153. Ok(Message::Cancel) => {
  154. log::info!("User canceled");
  155. std::process::exit(EXIT_CODE);
  156. }
  157. _ => {
  158. log::error!("Unexpected message");
  159. std::process::exit(EXIT_CODE);
  160. }
  161. }
  162. }
  163. let username = username.lock().unwrap().clone();
  164. let su_user = if username == acitve_user {
  165. None
  166. } else {
  167. Some(username)
  168. };
  169. match unsafe { forkpty(None, None) } {
  170. Ok(forkpty_result) => match forkpty_result {
  171. ForkptyResult::Parent { child, master } => {
  172. if let Err(e) = ui_parent(
  173. child,
  174. master,
  175. tx_to_ui_clone,
  176. rx_from_ui,
  177. su_user.is_some(),
  178. initial_password,
  179. ) {
  180. log::error!("Parent error: {:?}", e);
  181. kill_child(child);
  182. std::process::exit(EXIT_CODE);
  183. }
  184. }
  185. ForkptyResult::Child => {
  186. if let Err(e) = child(su_user, args) {
  187. log::error!("Child error: {:?}", e);
  188. std::process::exit(EXIT_CODE);
  189. }
  190. }
  191. },
  192. Err(err) => {
  193. log::error!("forkpty error: {:?}", err);
  194. if let Err(e) =
  195. tx_to_ui.send(Message::ErrorDialog(format!("Forkpty error: {:?}", err)))
  196. {
  197. log::error!("Channel error: {e:?}");
  198. std::process::exit(EXIT_CODE);
  199. }
  200. }
  201. }
  202. });
  203. let _holder = application.hold();
  204. let args: Vec<&str> = vec![];
  205. application.run_with_args(&args);
  206. log::debug!("exit from gtk::Application::run_with_args");
  207. std::process::exit(EXIT_CODE);
  208. }
  209. fn cmd_parent(child: Pid, master: OwnedFd) -> ResultType<()> {
  210. let raw_fd = master.as_raw_fd();
  211. if unsafe { fcntl(raw_fd, nix::libc::F_SETFL, nix::libc::O_NONBLOCK) } != 0 {
  212. let errno = std::io::Error::last_os_error();
  213. bail!("fcntl error: {errno:?}");
  214. }
  215. let mut file = unsafe { File::from_raw_fd(raw_fd) };
  216. let mut stdout = std::io::stdout();
  217. let stdin = std::io::stdin();
  218. let stdin_fd = stdin.as_raw_fd();
  219. let old_termios = termios::Termios::from_fd(stdin_fd)?;
  220. turn_off_echo(stdin_fd).ok();
  221. shutdown_hooks::add_shutdown_hook(turn_on_echo_shutdown_hook);
  222. let (tx, rx) = channel::<Vec<u8>>();
  223. std::thread::spawn(move || loop {
  224. let mut line = String::default();
  225. match stdin.read_line(&mut line) {
  226. Ok(0) => {
  227. kill_child(child);
  228. break;
  229. }
  230. Ok(_) => {
  231. if let Err(e) = tx.send(line.as_bytes().to_vec()) {
  232. log::error!("Channel error: {e:?}");
  233. kill_child(child);
  234. break;
  235. }
  236. }
  237. Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {}
  238. Err(e) => {
  239. log::info!("Failed to read stdin: {e:?}");
  240. kill_child(child);
  241. break;
  242. }
  243. };
  244. });
  245. loop {
  246. let mut buf = [0; 1024];
  247. match file.read(&mut buf) {
  248. Ok(0) => {
  249. log::info!("read from child: EOF");
  250. break;
  251. }
  252. Ok(n) => {
  253. let buf = String::from_utf8_lossy(&buf[..n]).to_string();
  254. print!("{}", buf);
  255. if let Err(e) = stdout.flush() {
  256. log::error!("flush failed: {e:?}");
  257. kill_child(child);
  258. break;
  259. }
  260. }
  261. Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
  262. std::thread::sleep(std::time::Duration::from_millis(50));
  263. }
  264. Err(e) => {
  265. // Child process is dead
  266. log::info!("Read child error: {:?}", e);
  267. break;
  268. }
  269. }
  270. match rx.try_recv() {
  271. Ok(v) => {
  272. if let Err(e) = file.write_all(&v) {
  273. log::error!("write error: {e:?}");
  274. kill_child(child);
  275. break;
  276. }
  277. }
  278. Err(e) => match e {
  279. std::sync::mpsc::TryRecvError::Empty => {}
  280. std::sync::mpsc::TryRecvError::Disconnected => {
  281. log::error!("receive error: {e:?}");
  282. kill_child(child);
  283. break;
  284. }
  285. },
  286. }
  287. }
  288. // Wait for child process
  289. let status = waitpid(child, None);
  290. log::info!("waitpid status: {:?}", status);
  291. let mut code = EXIT_CODE;
  292. match status {
  293. Ok(s) => match s {
  294. nix::sys::wait::WaitStatus::Exited(_pid, status) => {
  295. code = status;
  296. }
  297. _ => {}
  298. },
  299. Err(_) => {}
  300. }
  301. termios::tcsetattr(stdin_fd, termios::TCSANOW, &old_termios).ok();
  302. std::process::exit(code);
  303. }
  304. fn ui_parent(
  305. child: Pid,
  306. master: OwnedFd,
  307. tx_to_ui: Sender<Message>,
  308. rx_from_ui: Receiver<Message>,
  309. is_su: bool,
  310. initial_password: Option<String>,
  311. ) -> ResultType<()> {
  312. let mut initial_password = initial_password;
  313. let raw_fd = master.as_raw_fd();
  314. if unsafe { fcntl(raw_fd, nix::libc::F_SETFL, nix::libc::O_NONBLOCK) } != 0 {
  315. let errno = std::io::Error::last_os_error();
  316. tx_to_ui.send(Message::ErrorDialog(format!("fcntl error: {errno:?}")))?;
  317. bail!("fcntl error: {errno:?}");
  318. }
  319. let mut file = unsafe { File::from_raw_fd(raw_fd) };
  320. let mut first = initial_password.is_none();
  321. let mut su_password_sent = false;
  322. let mut saved_output = String::default();
  323. loop {
  324. let mut buf = [0; 1024];
  325. match file.read(&mut buf) {
  326. Ok(0) => {
  327. log::info!("read from child: EOF");
  328. break;
  329. }
  330. Ok(n) => {
  331. saved_output = String::default();
  332. let buf = String::from_utf8_lossy(&buf[..n]).trim().to_string();
  333. let last_line = buf.lines().last().unwrap_or(&buf).trim().to_string();
  334. log::info!("read from child: {}", buf);
  335. if last_line.starts_with("sudo:") || last_line.starts_with("su:") {
  336. if let Err(e) = tx_to_ui.send(Message::ErrorDialog(last_line)) {
  337. log::error!("Channel error: {e:?}");
  338. kill_child(child);
  339. }
  340. break;
  341. } else if last_line.ends_with(":") {
  342. match get_echo_turn_off(raw_fd) {
  343. Ok(true) => {
  344. log::debug!("get_echo_turn_off ok");
  345. if let Some(password) = initial_password.clone() {
  346. let v = format!("{}\n", password);
  347. if let Err(e) = file.write_all(v.as_bytes()) {
  348. let e = format!("Failed to send password: {e:?}");
  349. if let Err(e) = tx_to_ui.send(Message::ErrorDialog(e)) {
  350. log::error!("Channel error: {e:?}");
  351. }
  352. kill_child(child);
  353. break;
  354. }
  355. if is_su && !su_password_sent {
  356. su_password_sent = true;
  357. continue;
  358. }
  359. initial_password = None;
  360. continue;
  361. }
  362. // In fact, su mode can only input password once
  363. let err_msg = if first { "" } else { "Sorry, try again." };
  364. first = false;
  365. if let Err(e) =
  366. tx_to_ui.send(Message::PasswordPrompt((err_msg.to_string(), false)))
  367. {
  368. log::error!("Channel error: {e:?}");
  369. kill_child(child);
  370. break;
  371. }
  372. match rx_from_ui.recv() {
  373. Ok(Message::Password((_, password))) => {
  374. let v = format!("{}\n", password);
  375. if let Err(e) = file.write_all(v.as_bytes()) {
  376. let e = format!("Failed to send password: {e:?}");
  377. if let Err(e) = tx_to_ui.send(Message::ErrorDialog(e)) {
  378. log::error!("Channel error: {e:?}");
  379. }
  380. kill_child(child);
  381. break;
  382. }
  383. }
  384. Ok(Message::Cancel) => {
  385. log::info!("User canceled");
  386. kill_child(child);
  387. break;
  388. }
  389. _ => {
  390. log::error!("Unexpected message");
  391. break;
  392. }
  393. }
  394. }
  395. Ok(false) => log::warn!("get_echo_turn_off timeout"),
  396. Err(e) => log::error!("get_echo_turn_off error: {:?}", e),
  397. }
  398. } else {
  399. saved_output = buf.clone();
  400. if !last_line.is_empty() && initial_password.is_some() {
  401. log::error!("received not empty line: {last_line}, clear initial password");
  402. initial_password = None;
  403. }
  404. }
  405. }
  406. Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
  407. std::thread::sleep(std::time::Duration::from_millis(50));
  408. }
  409. Err(e) => {
  410. // Child process is dead
  411. log::debug!("Read error: {:?}", e);
  412. break;
  413. }
  414. }
  415. }
  416. // Wait for child process
  417. let status = waitpid(child, None);
  418. log::info!("waitpid status: {:?}", status);
  419. let mut code = EXIT_CODE;
  420. match status {
  421. Ok(s) => match s {
  422. nix::sys::wait::WaitStatus::Exited(_pid, status) => {
  423. code = status;
  424. }
  425. _ => {}
  426. },
  427. Err(_) => {}
  428. }
  429. if code != 0 && !saved_output.is_empty() {
  430. if let Err(e) = tx_to_ui.send(Message::ErrorDialog(saved_output.clone())) {
  431. log::error!("Channel error: {e:?}");
  432. std::process::exit(code);
  433. }
  434. return Ok(());
  435. }
  436. if let Err(e) = tx_to_ui.send(Message::Exit(code)) {
  437. log::error!("Channel error: {e:?}");
  438. std::process::exit(code);
  439. }
  440. Ok(())
  441. }
  442. fn child(su_user: Option<String>, args: Vec<String>) -> ResultType<()> {
  443. // https://doc.rust-lang.org/std/env/consts/constant.OS.html
  444. let os = std::env::consts::OS;
  445. let bsd = os == "freebsd" || os == "dragonfly" || os == "netbsd" || os == "openbad";
  446. let mut params = vec!["sudo".to_string()];
  447. if su_user.is_some() {
  448. params.push("-S".to_string());
  449. }
  450. params.push(CMD_SH.to_string());
  451. params.push("-c".to_string());
  452. let command = args
  453. .iter()
  454. .map(|s| {
  455. if su_user.is_some() {
  456. s.to_string()
  457. } else {
  458. quote_shell_arg(s, true)
  459. }
  460. })
  461. .collect::<Vec<String>>()
  462. .join(" ");
  463. let mut command = if bsd {
  464. let lc = match std::env::var("LC_ALL") {
  465. Ok(lc_all) => {
  466. if lc_all.contains('\'') {
  467. eprintln!(
  468. "sudo: Detected attempt to inject privileged command via LC_ALL env({lc_all}). Exiting!\n",
  469. );
  470. std::process::exit(EXIT_CODE);
  471. }
  472. format!("LC_ALL='{lc_all}' ")
  473. }
  474. Err(_) => {
  475. format!("unset LC_ALL;")
  476. }
  477. };
  478. format!("{}exec {}", lc, command)
  479. } else {
  480. command.to_string()
  481. };
  482. if su_user.is_some() {
  483. command = format!("'{}'", quote_shell_arg(&command, false));
  484. }
  485. params.push(command);
  486. std::env::set_var("LC_ALL", "C");
  487. if let Some(user) = &su_user {
  488. let su_subcommand = params
  489. .iter()
  490. .map(|p| p.to_string())
  491. .collect::<Vec<String>>()
  492. .join(" ");
  493. params = vec![
  494. "su".to_string(),
  495. "-".to_string(),
  496. user.to_string(),
  497. "-c".to_string(),
  498. su_subcommand,
  499. ];
  500. }
  501. // allow failure here
  502. let _ = setsid();
  503. let mut cparams = vec![];
  504. for param in &params {
  505. cparams.push(CString::new(param.as_str())?);
  506. }
  507. let su_or_sudo = if su_user.is_some() { "su" } else { "sudo" };
  508. let res = execvp(CString::new(su_or_sudo)?.as_c_str(), &cparams);
  509. eprintln!("sudo: execvp error: {:?}", res);
  510. std::process::exit(EXIT_CODE);
  511. }
  512. fn get_echo_turn_off(fd: RawFd) -> Result<bool, Error> {
  513. let tios = termios::Termios::from_fd(fd)?;
  514. for _ in 0..10 {
  515. if tios.c_lflag & termios::ECHO == 0 {
  516. return Ok(true);
  517. }
  518. std::thread::sleep(std::time::Duration::from_millis(10));
  519. }
  520. Ok(false)
  521. }
  522. fn turn_off_echo(fd: RawFd) -> Result<(), Error> {
  523. use termios::*;
  524. let mut termios = Termios::from_fd(fd)?;
  525. // termios.c_lflag &= !(ECHO | ECHONL | ICANON | IEXTEN);
  526. termios.c_lflag &= !ECHO;
  527. tcsetattr(fd, TCSANOW, &termios)?;
  528. Ok(())
  529. }
  530. pub extern "C" fn turn_on_echo_shutdown_hook() {
  531. let fd = std::io::stdin().as_raw_fd();
  532. if let Ok(mut termios) = termios::Termios::from_fd(fd) {
  533. termios.c_lflag |= termios::ECHO;
  534. termios::tcsetattr(fd, termios::TCSANOW, &termios).ok();
  535. }
  536. }
  537. fn kill_child(child: Pid) {
  538. unsafe { kill(child.as_raw(), Signal::SIGINT as _) };
  539. let mut res = 0;
  540. for _ in 0..10 {
  541. match waitpid(child, Some(WaitPidFlag::WNOHANG)) {
  542. Ok(_) => {
  543. res = 1;
  544. break;
  545. }
  546. Err(_) => (),
  547. }
  548. std::thread::sleep(std::time::Duration::from_millis(100));
  549. }
  550. if res == 0 {
  551. log::info!("Force killing child process");
  552. unsafe { kill(child.as_raw(), Signal::SIGKILL as _) };
  553. }
  554. }
  555. fn password_prompt(
  556. username: &str,
  557. last_password: &str,
  558. err: &str,
  559. show_edit: bool,
  560. ) -> Option<(String, String)> {
  561. let dialog = gtk::Dialog::builder()
  562. .title(crate::get_app_name())
  563. .modal(true)
  564. .build();
  565. // https://docs.gtk.org/gtk4/method.Dialog.set_default_response.html
  566. dialog.set_default_response(gtk::ResponseType::Ok);
  567. let content_area = dialog.content_area();
  568. let label = gtk::Label::builder()
  569. .label(translate("Authentication Required".to_string()))
  570. .margin_top(10)
  571. .build();
  572. content_area.add(&label);
  573. let image = gtk::Image::from_icon_name(Some("avatar-default-symbolic"), gtk::IconSize::Dialog);
  574. image.set_margin_top(10);
  575. content_area.add(&image);
  576. let user_label = gtk::Label::new(Some(username));
  577. let edit_button = gtk::Button::new();
  578. edit_button.set_relief(gtk::ReliefStyle::None);
  579. let edit_icon =
  580. gtk::Image::from_icon_name(Some("document-edit-symbolic"), gtk::IconSize::Button.into());
  581. edit_button.set_image(Some(&edit_icon));
  582. edit_button.set_can_focus(false);
  583. let user_entry = gtk::Entry::new();
  584. user_entry.set_alignment(0.5);
  585. user_entry.set_width_request(100);
  586. let user_box = gtk::Box::new(gtk::Orientation::Horizontal, 5);
  587. user_box.add(&user_label);
  588. user_box.add(&edit_button);
  589. user_box.add(&user_entry);
  590. user_box.set_halign(gtk::Align::Center);
  591. user_box.set_valign(gtk::Align::Center);
  592. user_box.set_vexpand(true);
  593. content_area.add(&user_box);
  594. edit_button.connect_clicked(
  595. glib::clone!(@weak user_label, @weak edit_button, @weak user_entry=> move |_| {
  596. let username = user_label.text().to_string();
  597. user_entry.set_text(&username);
  598. user_label.hide();
  599. edit_button.hide();
  600. user_entry.show();
  601. user_entry.grab_focus();
  602. }),
  603. );
  604. let password_input = gtk::Entry::builder()
  605. .visibility(false)
  606. .input_purpose(gtk::InputPurpose::Password)
  607. .placeholder_text(translate("Password".to_string()))
  608. .margin_top(20)
  609. .margin_start(30)
  610. .margin_end(30)
  611. .activates_default(true)
  612. .text(last_password)
  613. .build();
  614. password_input.set_alignment(0.5);
  615. // https://docs.gtk.org/gtk3/signal.Entry.activate.html
  616. password_input.connect_activate(glib::clone!(@weak dialog => move |_| {
  617. dialog.response(gtk::ResponseType::Ok);
  618. }));
  619. content_area.add(&password_input);
  620. user_entry.connect_focus_out_event(
  621. glib::clone!(@weak user_label, @weak edit_button, @weak user_entry, @weak password_input => @default-return glib::Propagation::Proceed, move |_, _| {
  622. let username = user_entry.text().to_string();
  623. user_label.set_text(&username);
  624. user_entry.hide();
  625. user_label.show();
  626. edit_button.show();
  627. glib::Propagation::Proceed
  628. }),
  629. );
  630. user_entry.connect_activate(
  631. glib::clone!(@weak user_label, @weak edit_button, @weak user_entry, @weak password_input => move |_| {
  632. let username = user_entry.text().to_string();
  633. user_label.set_text(&username);
  634. user_entry.hide();
  635. user_label.show();
  636. edit_button.show();
  637. password_input.grab_focus();
  638. }),
  639. );
  640. if !err.is_empty() {
  641. let err_label = gtk::Label::new(None);
  642. err_label.set_markup(&format!(
  643. "<span font='10' foreground='orange'>{}</span>",
  644. err
  645. ));
  646. err_label.set_selectable(true);
  647. content_area.add(&err_label);
  648. }
  649. let cancel_button = gtk::Button::builder()
  650. .label(translate("Cancel".to_string()))
  651. .hexpand(true)
  652. .build();
  653. cancel_button.connect_clicked(glib::clone!(@weak dialog => move |_| {
  654. dialog.response(gtk::ResponseType::Cancel);
  655. }));
  656. let authenticate_button = gtk::Button::builder()
  657. .label(translate("Authenticate".to_string()))
  658. .hexpand(true)
  659. .build();
  660. authenticate_button.connect_clicked(glib::clone!(@weak dialog => move |_| {
  661. dialog.response(gtk::ResponseType::Ok);
  662. }));
  663. let button_box = gtk::Box::builder()
  664. .orientation(gtk::Orientation::Horizontal)
  665. .hexpand(true)
  666. .homogeneous(true)
  667. .spacing(10)
  668. .margin_top(10)
  669. .build();
  670. button_box.add(&cancel_button);
  671. button_box.add(&authenticate_button);
  672. content_area.add(&button_box);
  673. content_area.set_spacing(10);
  674. content_area.set_border_width(10);
  675. dialog.set_width_request(400);
  676. dialog.show_all();
  677. dialog.set_position(gtk::WindowPosition::Center);
  678. dialog.set_keep_above(true);
  679. password_input.grab_focus();
  680. user_entry.hide();
  681. if !show_edit {
  682. edit_button.hide();
  683. }
  684. dialog.check_resize();
  685. let response = dialog.run();
  686. dialog.hide();
  687. if response == gtk::ResponseType::Ok {
  688. let username = if user_entry.get_visible() {
  689. user_entry.text().to_string()
  690. } else {
  691. user_label.text().to_string()
  692. };
  693. Some((username, password_input.text().to_string()))
  694. } else {
  695. None
  696. }
  697. }
  698. fn error_dialog_and_exit(err_msg: &str, exit_code: i32) {
  699. log::error!("Error dialog: {err_msg}, exit code: {exit_code}");
  700. let dialog = gtk::MessageDialog::builder()
  701. .message_type(gtk::MessageType::Error)
  702. .title(crate::get_app_name())
  703. .text("Error")
  704. .secondary_text(err_msg)
  705. .modal(true)
  706. .buttons(gtk::ButtonsType::Ok)
  707. .build();
  708. dialog.set_position(gtk::WindowPosition::Center);
  709. dialog.set_keep_above(true);
  710. dialog.run();
  711. dialog.close();
  712. std::process::exit(exit_code);
  713. }
  714. fn quote_shell_arg(arg: &str, add_splash_if_match: bool) -> String {
  715. let mut rv = arg.to_string();
  716. let re = hbb_common::regex::Regex::new("(\\s|[][!\"#$&'()*,;<=>?\\^`{}|~])");
  717. let Ok(re) = re else {
  718. return rv;
  719. };
  720. if re.is_match(arg) {
  721. rv = rv.replace("'", "'\\''");
  722. if add_splash_if_match {
  723. rv = format!("'{}'", rv);
  724. }
  725. }
  726. rv
  727. }