gtk_sudo.rs 27 KB

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