diff --git a/Cargo.lock b/Cargo.lock index 5a4cad9f8..9d2984abe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4159,6 +4159,38 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "pam" +version = "0.7.0" +source = "git+https://github.com/fufesou/pam#10da2cbbabe32cbc9de22a66abe44738e7ec0ea0" +dependencies = [ + "libc", + "pam-macros", + "pam-sys", + "users 0.10.0", +] + +[[package]] +name = "pam-macros" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c94f3b9b97df3c6d4e51a14916639b24e02c7d15d1dba686ce9b1118277cb811" +dependencies = [ + "proc-macro2 1.0.54", + "quote 1.0.26", + "syn 1.0.109", +] + +[[package]] +name = "pam-sys" +version = "1.0.0-alpha4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e9dfd42858f6a6bb1081079fd9dc259ca3e2aaece6cb689fd36b1058046c969" +dependencies = [ + "bindgen 0.59.2", + "libc", +] + [[package]] name = "pango" version = "0.16.5" @@ -5124,6 +5156,7 @@ dependencies = [ "objc", "objc_id", "os-version", + "pam", "parity-tokio-ipc", "rdev", "repng", @@ -5149,6 +5182,7 @@ dependencies = [ "tray-icon", "trayicon", "url", + "users 0.11.0", "uuid", "virtual_display", "whoami", @@ -6443,6 +6477,26 @@ dependencies = [ "serde 1.0.159", ] +[[package]] +name = "users" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa4227e95324a443c9fcb06e03d4d85e91aabe9a5a02aa818688b6918b6af486" +dependencies = [ + "libc", + "log", +] + +[[package]] +name = "users" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032" +dependencies = [ + "libc", + "log", +] + [[package]] name = "utf8parse" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index a01415837..10f90f387 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -122,6 +122,8 @@ mouce = { git="https://github.com/fufesou/mouce.git" } evdev = { git="https://github.com/fufesou/evdev" } dbus = "0.9" dbus-crossroads = "0.5" +pam = { git="https://github.com/fufesou/pam" } +users = "0.11.0" [target.'cfg(target_os = "android")'.dependencies] android_logger = "0.11" diff --git a/libs/hbb_common/protos/message.proto b/libs/hbb_common/protos/message.proto index a481ae6c4..c49f163a8 100644 --- a/libs/hbb_common/protos/message.proto +++ b/libs/hbb_common/protos/message.proto @@ -52,6 +52,11 @@ message FileTransfer { bool show_hidden = 2; } +message OSLogin { + string username = 1; + string password = 2; +} + message LoginRequest { string username = 1; bytes password = 2; @@ -65,6 +70,7 @@ message LoginRequest { bool video_ack_required = 9; uint64 session_id = 10; string version = 11; + OSLogin os_login = 12; } message ChatMessage { string text = 1; } diff --git a/libs/hbb_common/src/platform/linux.rs b/libs/hbb_common/src/platform/linux.rs index 89c96799d..1d826ea97 100644 --- a/libs/hbb_common/src/platform/linux.rs +++ b/libs/hbb_common/src/platform/linux.rs @@ -191,7 +191,7 @@ pub fn run_cmds(cmds: &str) -> ResultType { } #[cfg(not(feature = "flatpak"))] -fn run_loginctl(args: Option>) -> std::io::Result { +pub(super) fn run_loginctl(args: Option>) -> std::io::Result { let mut cmd = std::process::Command::new("loginctl"); if let Some(a) = args { return cmd.args(a).output(); @@ -200,7 +200,7 @@ fn run_loginctl(args: Option>) -> std::io::Result>) -> std::io::Result { +pub(super) fn run_loginctl(args: Option>) -> std::io::Result { let mut l_args = String::from("loginctl"); if let Some(a) = args { l_args = format!("{} {}", l_args, a.join(" ")); diff --git a/res/pam.d/rustdesk.debian b/res/pam.d/rustdesk.debian new file mode 100644 index 000000000..789ce8f7c --- /dev/null +++ b/res/pam.d/rustdesk.debian @@ -0,0 +1,5 @@ +#%PAM-1.0 +@include common-auth +@include common-account +@include common-session +@include common-password diff --git a/res/pam.d/rustdesk.suse b/res/pam.d/rustdesk.suse new file mode 100644 index 000000000..a7c7836ce --- /dev/null +++ b/res/pam.d/rustdesk.suse @@ -0,0 +1,5 @@ +#%PAM-1.0 +auth include common-auth +account include common-account +session include common-session +password include common-password diff --git a/res/startwm.sh b/res/startwm.sh new file mode 100755 index 000000000..7cdaf07ce --- /dev/null +++ b/res/startwm.sh @@ -0,0 +1,130 @@ +#!/usr/bin/env bash + +# This script is derived from https://github.com/neutrinolabs/xrdp/sesman/startwm.sh. + +# +# This script is an example. You might need to edit this script +# depending on your distro if it doesn't work for you. +# +# Uncomment the following line for debug: +# exec xterm + + +# Execution sequence for interactive login shell - pseudocode +# +# IF /etc/profile is readable THEN +# execute ~/.bash_profile +# END IF +# IF ~/.bash_profile is readable THEN +# execute ~/.bash_profile +# ELSE +# IF ~/.bash_login is readable THEN +# execute ~/.bash_login +# ELSE +# IF ~/.profile is readable THEN +# execute ~/.profile +# END IF +# END IF +# END IF +pre_start() +{ + if [ -r /etc/profile ]; then + . /etc/profile + fi + if [ -r ~/.bash_profile ]; then + . ~/.bash_profile + else + if [ -r ~/.bash_login ]; then + . ~/.bash_login + else + if [ -r ~/.profile ]; then + . ~/.profile + fi + fi + fi + return 0 +} + +# When loging out from the interactive shell, the execution sequence is: +# +# IF ~/.bash_logout exists THEN +# execute ~/.bash_logout +# END IF +post_start() +{ + if [ -r ~/.bash_logout ]; then + . ~/.bash_logout + fi + return 0 +} + +#start the window manager +wm_start() +{ + if [ -r /etc/default/locale ]; then + . /etc/default/locale + export LANG LANGUAGE + fi + + # debian + if [ -r /etc/X11/Xsession ]; then + pre_start + . /etc/X11/Xsession + post_start + exit 0 + fi + + # alpine + # Don't use /etc/X11/xinit/Xsession - it doesn't work + if [ -f /etc/alpine-release ]; then + if [ -f /etc/X11/xinit/xinitrc ]; then + pre_start + /etc/X11/xinit/xinitrc + post_start + else + echo "** xinit package isn't installed" >&2 + exit 1 + fi + fi + + # el + if [ -r /etc/X11/xinit/Xsession ]; then + pre_start + . /etc/X11/xinit/Xsession + post_start + exit 0 + fi + + # suse + if [ -r /etc/X11/xdm/Xsession ]; then + # since the following script run a user login shell, + # do not execute the pseudo login shell scripts + . /etc/X11/xdm/Xsession + exit 0 + elif [ -r /usr/etc/X11/xdm/Xsession ]; then + . /usr/etc/X11/xdm/Xsession + exit 0 + fi + + pre_start + xterm + post_start +} + +#. /etc/environment +#export PATH=$PATH +#export LANG=$LANG + +# change PATH to be what your environment needs usually what is in +# /etc/environment +#PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games" +#export PATH=$PATH + +# for PATH and LANG from /etc/environment +# pam will auto process the environment file if /etc/pam.d/xrdp-sesman +# includes +# auth required pam_env.so readenv=1 + +wm_start + +exit 1 diff --git a/res/xorg.conf b/res/xorg.conf new file mode 100644 index 000000000..fe1539995 --- /dev/null +++ b/res/xorg.conf @@ -0,0 +1,30 @@ +Section "Monitor" + Identifier "Dummy Monitor" + + # Default HorizSync 31.50 - 48.00 kHz + HorizSync 5.0 - 150.0 + # Default VertRefresh 50.00 - 70.00 Hz + VertRefresh 5.0 - 100.0 + + # Taken from https://www.xpra.org/xorg.conf + Modeline "1920x1080" 23.53 1920 1952 2040 2072 1080 1106 1108 1135 + Modeline "1280x720" 27.41 1280 1312 1416 1448 720 737 740 757 +EndSection + +Section "Device" + Identifier "Dummy VideoCard" + Driver "dummy" + # Default VideoRam 4096 + # (1920 * 1080 * 4) / 1024 = 8100 + VideoRam 8100 +EndSection + +Section "Screen" + Identifier "Dummy Screen" + Device "Dummy VideoCard" + Monitor "Dummy Monitor" + SubSection "Display" + Depth 24 + Modes "1920x1080" "1280x720" + EndSubSection +EndSection \ No newline at end of file diff --git a/src/cli.rs b/src/cli.rs index 454eec1ee..13e70987b 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -85,8 +85,8 @@ impl Interface for Session { handle_hash(self.lc.clone(), &pass, hash, self, peer).await; } - async fn handle_login_from_ui(&mut self, password: String, remember: bool, peer: &mut Stream) { - handle_login_from_ui(self.lc.clone(), password, remember, peer).await; + async fn handle_login_from_ui(&mut self, os_username: String, os_password: String, password: String, remember: bool, peer: &mut Stream) { + handle_login_from_ui(self.lc.clone(), os_username, os_password, password, remember, peer).await; } async fn handle_test_delay(&mut self, t: TestDelay, peer: &mut Stream) { diff --git a/src/client.rs b/src/client.rs index 2f745b70c..f03cb0e55 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1591,7 +1591,12 @@ impl LoginConfigHandler { } /// Create a [`Message`] for login. - fn create_login_msg(&self, password: Vec) -> Message { + fn create_login_msg( + &self, + os_username: String, + os_password: String, + password: Vec, + ) -> Message { #[cfg(any(target_os = "android", target_os = "ios"))] let my_id = Config::get_id_or(crate::common::DEVICE_ID.lock().unwrap().clone()); #[cfg(not(any(target_os = "android", target_os = "ios")))] @@ -1604,6 +1609,12 @@ impl LoginConfigHandler { option: self.get_option_message(true).into(), session_id: self.session_id, version: crate::VERSION.to_string(), + os_login: Some(OSLogin { + username: os_username, + password: os_password, + ..Default::default() + }) + .into(), ..Default::default() }; match self.conn_type { @@ -1908,10 +1919,46 @@ pub fn handle_login_error( err: &str, interface: &impl Interface, ) -> bool { - if err == "Wrong Password" { + if err == crate::server::LOGIN_MSG_PASSWORD_EMPTY { + lc.write().unwrap().password = Default::default(); + interface.msgbox("input-password", "Password Required", "", ""); + true + } else if err == crate::server::LOGIN_MSG_PASSWORD_WRONG { lc.write().unwrap().password = Default::default(); interface.msgbox("re-input-password", err, "Do you want to enter again?", ""); true + } else if err == crate::server::LOGIN_MSG_XSESSION_NOT_READY { + interface.msgbox( + "xsession-login", + "xsession is unready", + "Input linux user/password", + "", + ); + true + } else if err == crate::server::LOGIN_MSG_XSESSION_FAILED { + interface.msgbox( + "xsession-re-login", + "xsession username/password is wrong", + "Do you want to enter again?", + "", + ); + true + } else if err == crate::server::LOGIN_MSG_XSESSION_NOT_READY_PASSWORD_EMPTY { + interface.msgbox( + "xsession-login-password", + "xsession is unready", + "Input connection password and linux user/password", + "", + ); + true + } else if err == crate::server::LOGIN_MSG_XSESSION_NOT_READY_PASSWORD_WRONG { + interface.msgbox( + "xsession-login-re-password", + "xsession is unready and password is wrong", + "Do you want to enter again?", + "", + ); + true } else if err == "No Password Access" { lc.write().unwrap().password = Default::default(); interface.msgbox( @@ -1944,7 +1991,7 @@ pub async fn handle_hash( lc: Arc>, password_preset: &str, hash: Hash, - interface: &impl Interface, + _interface: &impl Interface, peer: &mut Stream, ) { lc.write().unwrap().hash = hash.clone(); @@ -1970,13 +2017,19 @@ pub async fn handle_hash( } if password.is_empty() { // login without password, the remote side can click accept - send_login(lc.clone(), Vec::new(), peer).await; - interface.msgbox("input-password", "Password Required", "", ""); + send_login(lc.clone(), "".to_owned(), "".to_owned(), Vec::new(), peer).await; } else { let mut hasher = Sha256::new(); hasher.update(&password); hasher.update(&hash.challenge); - send_login(lc.clone(), hasher.finalize()[..].into(), peer).await; + send_login( + lc.clone(), + "".to_owned(), + "".to_owned(), + hasher.finalize()[..].into(), + peer, + ) + .await; } lc.write().unwrap().hash = hash; } @@ -1986,10 +2039,21 @@ pub async fn handle_hash( /// # Arguments /// /// * `lc` - Login config. +/// * `os_username` - OS username. +/// * `os_password` - OS password. /// * `password` - Password. /// * `peer` - [`Stream`] for communicating with peer. -async fn send_login(lc: Arc>, password: Vec, peer: &mut Stream) { - let msg_out = lc.read().unwrap().create_login_msg(password); +async fn send_login( + lc: Arc>, + os_username: String, + os_password: String, + password: Vec, + peer: &mut Stream, +) { + let msg_out = lc + .read() + .unwrap() + .create_login_msg(os_username, os_password, password); allow_err!(peer.send(&msg_out).await); } @@ -1998,25 +2062,40 @@ async fn send_login(lc: Arc>, password: Vec, peer /// # Arguments /// /// * `lc` - Login config. +/// * `os_username` - OS username. +/// * `os_password` - OS password. /// * `password` - Password. /// * `remember` - Whether to remember password. /// * `peer` - [`Stream`] for communicating with peer. pub async fn handle_login_from_ui( lc: Arc>, + os_username: String, + os_password: String, password: String, remember: bool, peer: &mut Stream, ) { - let mut hasher = Sha256::new(); - hasher.update(password); - hasher.update(&lc.read().unwrap().hash.salt); - let res = hasher.finalize(); - lc.write().unwrap().remember = remember; - lc.write().unwrap().password = res[..].into(); + let mut hash_password = if password.is_empty() { + let mut password2 = lc.read().unwrap().password.clone(); + if password2.is_empty() { + password2 = lc.read().unwrap().config.password.clone(); + } + password2 + } else { + let mut hasher = Sha256::new(); + hasher.update(password); + hasher.update(&lc.read().unwrap().hash.salt); + let res = hasher.finalize(); + lc.write().unwrap().remember = remember; + lc.write().unwrap().password = res[..].into(); + res[..].into() + }; let mut hasher2 = Sha256::new(); - hasher2.update(&res[..]); + hasher2.update(&hash_password[..]); hasher2.update(&lc.read().unwrap().hash.challenge); - send_login(lc.clone(), hasher2.finalize()[..].into(), peer).await; + hash_password = hasher2.finalize()[..].to_vec(); + + send_login(lc.clone(), os_username, os_password, hash_password, peer).await; } async fn send_switch_login_request( @@ -2030,7 +2109,7 @@ async fn send_switch_login_request( lr: hbb_common::protobuf::MessageField::some( lc.read() .unwrap() - .create_login_msg(vec![]) + .create_login_msg("".to_owned(), "".to_owned(), vec![]) .login_request() .to_owned(), ), @@ -2051,7 +2130,14 @@ pub trait Interface: Send + Clone + 'static + Sized { self.msgbox("error", "Error", err, ""); } async fn handle_hash(&mut self, pass: &str, hash: Hash, peer: &mut Stream); - async fn handle_login_from_ui(&mut self, password: String, remember: bool, peer: &mut Stream); + async fn handle_login_from_ui( + &mut self, + os_username: String, + os_password: String, + password: String, + remember: bool, + peer: &mut Stream, + ); async fn handle_test_delay(&mut self, t: TestDelay, peer: &mut Stream); fn get_login_config_handler(&self) -> Arc>; @@ -2071,7 +2157,7 @@ pub trait Interface: Send + Clone + 'static + Sized { #[derive(Clone)] pub enum Data { Close, - Login((String, bool)), + Login((String, String, String, bool)), Message(Message), SendFiles((i32, String, String, i32, bool, bool)), RemoveDirAll((i32, String, bool, bool)), diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index b0bddc82e..c1c38af40 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -360,9 +360,9 @@ impl Remote { allow_err!(peer.send(&msg).await); return false; } - Data::Login((password, remember)) => { + Data::Login((os_username, os_password, password, remember)) => { self.handler - .handle_login_from_ui(password, remember, peer) + .handle_login_from_ui(os_username, os_password, password, remember, peer) .await; } Data::ToggleClipboardFile => { diff --git a/src/platform/linux.rs b/src/platform/linux.rs index dda4b115b..c1da431a4 100644 --- a/src/platform/linux.rs +++ b/src/platform/linux.rs @@ -1,3 +1,4 @@ +use super::linux_desktop::GNOME_SESSION_BINARY; use super::{CursorData, ResultType}; use desktop::Desktop; pub use hbb_common::platform::linux::*; diff --git a/src/platform/linux_desktop.rs b/src/platform/linux_desktop.rs new file mode 100644 index 000000000..bf9f465d0 --- /dev/null +++ b/src/platform/linux_desktop.rs @@ -0,0 +1,951 @@ +use super::{linux::*, ResultType}; +use hbb_common::{allow_err, bail, log, rand::prelude::*, tokio::time}; +use pam; +use std::{ + collections::HashMap, + os::unix::process::CommandExt, + path::Path, + process::{Child, Command}, + sync::{ + atomic::{AtomicBool, Ordering}, + mpsc::{sync_channel, SyncSender}, + Arc, Mutex, + }, + time::{Duration, Instant}, +}; +use users::{get_user_by_name, os::unix::UserExt, User}; + +lazy_static::lazy_static! { + static ref DESKTOP_RUNNING: Arc = Arc::new(AtomicBool::new(false)); + static ref DESKTOP_INST: Arc>> = Arc::new(Mutex::new(None)); +} + +pub const VIRTUAL_X11_DESKTOP: &str = "xfce4"; +pub const VIRTUAL_X11_DESKTOP_START: &str = "startxfce4"; +pub const XFCE4_PANEL: &str = "xfce4-panel"; +pub const GNOME_SESSION_BINARY: &str = "gnome-session-binary"; +pub const ENV_DESKTOP_PROTOCAL: &str = "RUSTDESK_PROTOCAL"; +pub const ENV_DESKTOP_PROTOCAL_WAYLAND: &str = "wayland"; +pub const ENV_DESKTOP_PROTOCAL__X11: &str = "x11"; +pub const ENV_DESKTOP_PROTOCAL_UNKNOWN: &str = "unknown"; + +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum Protocal { + Wayland, + X11, // Xorg + Unknown, +} + +#[derive(Debug, Clone)] +pub struct DesktopEnv { + pub protocal: Protocal, + pub username: String, + pub uid: String, + pub display: String, + pub xauth: String, +} + +#[derive(Debug)] +pub struct Desktop { + env: DesktopEnv, + child_exit: Arc, + is_child_running: Arc, +} + +fn check_update_env() { + let mut inst = DESKTOP_INST.lock().unwrap(); + if let Some(inst) = &mut (*inst) { + if !inst.is_child_running.load(Ordering::SeqCst) { + inst.child_exit.store(true, Ordering::SeqCst); + let old_env = inst.env.clone(); + allow_err!(inst.env.refresh()); + if !inst.env.is_same_env(&old_env) { + inst.env.update_env(); + log::debug!("desktop env changed, {:?}", &inst.env); + } + } + } +} + +pub fn start_xdesktop() { + std::thread::spawn(|| { + if wait_xdesktop(20) { + log::info!("Wait desktop: default"); + } else { + log::info!("Wait desktop: none"); + } + *DESKTOP_INST.lock().unwrap() = Some(Desktop::new()); + + let interval = time::Duration::from_millis(super::SERVICE_INTERVAL); + DESKTOP_RUNNING.store(true, Ordering::SeqCst); + while DESKTOP_RUNNING.load(Ordering::SeqCst) { + check_update_env(); + std::thread::sleep(interval); + } + log::info!("xdesktop update thread exit"); + }); +} + +pub fn stop_xdesktop() { + DESKTOP_RUNNING.store(false, Ordering::SeqCst); +} + +pub fn get_desktop_env() -> Option { + match &*DESKTOP_INST.lock().unwrap() { + Some(inst) => Some(inst.env.clone()), + None => None, + } +} + +pub fn try_start_x_session(username: &str, password: &str) -> ResultType { + let mut inst = DESKTOP_INST.lock().unwrap(); + if let Some(inst) = &mut (*inst) { + let _ = inst.try_start_x_session(username, password)?; + log::debug!("try_start_x_session, username: {}, {:?}", &username, &inst); + Ok(inst.env.clone()) + } else { + bail!(crate::server::LOGIN_MSG_XDESKTOP_NOT_INITED); + } +} + +fn wait_xdesktop(timeout_secs: u64) -> bool { + let wait_begin = Instant::now(); + while wait_begin.elapsed().as_secs() < timeout_secs { + let seat0 = get_values_of_seat0(&[0]); + if !seat0[0].is_empty() { + return true; + } + + if let Ok(output) = run_cmds(format!( + "ps -ef | grep -v 'grep' | grep -E 'gnome-session-binary|{}'", + XFCE4_PANEL + )) { + if !output.is_empty() { + log::info!("wait xdesktop: find xclient {}", &output); + return true; + } + } + + std::thread::sleep(Duration::from_millis(super::SERVICE_INTERVAL)); + } + + false +} + +impl DesktopEnv { + pub fn new() -> Self { + let xauth = get_env_var("XAUTHORITY"); + + Self { + protocal: Protocal::Unknown, + username: "".to_owned(), + uid: "".to_owned(), + display: "".to_owned(), + xauth: if xauth.is_empty() { + "/tmp/.Xauthority".to_owned() + } else { + xauth + }, + } + } + + fn update_env(&self) { + if self.is_ready() { + std::env::set_var("DISPLAY", &self.display); + std::env::set_var("XAUTHORITY", &self.xauth); + std::env::set_var(ENV_DESKTOP_PROTOCAL, &self.protocal.to_string()); + } else { + std::env::set_var("DISPLAY", ""); + std::env::set_var("XAUTHORITY", ""); + std::env::set_var(ENV_DESKTOP_PROTOCAL, &Protocal::Unknown.to_string()); + } + } + + pub fn is_same_env(&self, other: &Self) -> bool { + self.protocal == other.protocal + && self.uid == other.uid + && self.display == other.display + && self.xauth == other.xauth + } + + #[inline(always)] + pub fn is_ready(&self) -> bool { + self.protocal == Protocal::X11 + } + + // The logic mainly fron https://github.com/neutrinolabs/xrdp/blob/34fe9b60ebaea59e8814bbc3ca5383cabaa1b869/sesman/session.c#L334. + fn get_avail_display() -> ResultType { + let display_range = 0..51; + for i in display_range.clone() { + if Self::is_x_server_running(i) { + continue; + } + return Ok(i); + } + bail!("No avaliable display found in range {:?}", display_range) + } + + fn is_x_server_running(display: u32) -> bool { + Path::new(&format!("/tmp/.X11-unix/X{}", display)).exists() + || Path::new(&format!("/tmp/.X{}-lock", display)).exists() + } + + fn get_display(&mut self) { + self.display = get_env_tries("DISPLAY", &self.uid, GNOME_SESSION_BINARY, 10); + if self.display.is_empty() { + self.display = get_env_tries("DISPLAY", &self.uid, XFCE4_PANEL, 10); + } + if self.display.is_empty() { + self.display = Self::get_display_by_user(&self.username); + } + if self.display.is_empty() { + self.display = ":0".to_owned(); + } + self.display = self + .display + .replace(&whoami::hostname(), "") + .replace("localhost", ""); + } + + fn get_xauth(&mut self) { + self.xauth = get_env_tries("XAUTHORITY", &self.uid, GNOME_SESSION_BINARY, 10); + if self.xauth.is_empty() { + get_env_tries("XAUTHORITY", &self.uid, XFCE4_PANEL, 10); + } + + let gdm = format!("/run/user/{}/gdm/Xauthority", self.uid); + if self.xauth.is_empty() { + self.xauth = if std::path::Path::new(&gdm).exists() { + gdm + } else { + let username = &self.username; + if username == "root" { + format!("/{}/.Xauthority", username) + } else { + let tmp = format!("/home/{}/.Xauthority", username); + if std::path::Path::new(&tmp).exists() { + tmp + } else { + format!("/var/lib/{}/.Xauthority", username) + } + } + }; + } + } + + // fixme: reduce loginctl + fn get_env_seat0(&mut self) -> ResultType { + let output = Command::new("loginctl").output()?; + for line in String::from_utf8_lossy(&output.stdout).lines() { + if line.contains("seat0") { + if let Some(sid) = line.split_whitespace().nth(0) { + if Self::is_active(sid)? { + if let Some(uid) = line.split_whitespace().nth(1) { + self.uid = uid.to_owned(); + } + if let Some(u) = line.split_whitespace().nth(2) { + self.username = u.to_owned(); + } + + self.protocal = Protocal::Unknown; + let type_output = Command::new("loginctl") + .args(vec!["show-session", "-p", "Type", sid]) + .output()?; + let type_stdout = String::from_utf8_lossy(&type_output.stdout); + + if type_stdout.contains("x11") { + self.protocal = Protocal::X11; + break; + } else if type_stdout.contains("wayland") { + self.protocal = Protocal::Wayland; + } + } + } + } + } + Ok(self.is_ready()) + } + + // some case, there is no seat0 https://github.com/rustdesk/rustdesk/issues/73 + fn get_env_active(&mut self) -> ResultType { + let output = Command::new("loginctl").output()?; + + // set active Xorg session + for line in String::from_utf8_lossy(&output.stdout).lines() { + if line.contains("sessions listed.") { + continue; + } + if let Some(sid) = line.split_whitespace().nth(0) { + if Self::is_active(sid)? { + if Self::get_display_server_of_session(sid) == ENV_DESKTOP_PROTOCAL__X11 { + if let Some(uid) = line.split_whitespace().nth(1) { + self.uid = uid.to_owned(); + } + if let Some(u) = line.split_whitespace().nth(2) { + self.username = u.to_owned(); + } + + self.protocal = Protocal::X11; + } + } + } + } + // // set active xfce4 session + // for line in String::from_utf8_lossy(&output.stdout).lines() { + // if let Some(sid) = line.split_whitespace().nth(0) { + // if Self::is_active(sid)? { + // let tty_output = Command::new("loginctl") + // .args(vec!["show-session", "-p", "TTY", sid]) + // .output()?; + // let tty: String = String::from_utf8_lossy(&tty_output.stdout) + // .replace("TTY=", "") + // .trim_end() + // .into(); + + // let xfce_panel_info = + // run_cmds(format!("ps -e | grep \"{}.\\\\+{}\"", tty, XFCE4_PANEL))?; + // if xfce_panel_info.trim_end().to_string() != "" { + // if let Some(uid) = line.split_whitespace().nth(1) { + // self.uid = uid.to_owned(); + // } + // if let Some(u) = line.split_whitespace().nth(2) { + // self.username = u.to_owned(); + // } + // } + // } + // } + // } + Ok(self.is_ready()) + } + + // fixme: dup + fn get_display_server_of_session(session: &str) -> String { + if let Ok(output) = Command::new("loginctl") + .args(vec!["show-session", "-p", "Type", session]) + .output() + // Check session type of the session + { + let display_server = String::from_utf8_lossy(&output.stdout) + .replace("Type=", "") + .trim_end() + .into(); + if display_server == "tty" { + // If the type is tty... + if let Ok(output) = Command::new("loginctl") + .args(vec!["show-session", "-p", "TTY", session]) + .output() + // Get the tty number + { + let tty: String = String::from_utf8_lossy(&output.stdout) + .replace("TTY=", "") + .trim_end() + .into(); + if let Ok(xorg_results) = + run_cmds(format!("ps -e | grep \"{}.\\\\+Xorg\"", tty)) + // And check if Xorg is running on that tty + { + if xorg_results.trim_end().to_string() != "" { + // If it is, manually return "x11", otherwise return tty + ENV_DESKTOP_PROTOCAL__X11.to_owned() + } else { + display_server + } + } else { + // If any of these commands fail just fall back to the display server + display_server + } + } else { + display_server + } + } else { + // If the session is not a tty, then just return the type as usual + display_server + } + } else { + "".to_owned() + } + } + + // fixme: remove + fn is_active(sid: &str) -> ResultType { + let output = Command::new("loginctl") + .args(vec!["show-session", "-p", "State", sid]) + .output()?; + + Ok(String::from_utf8_lossy(&output.stdout).contains("active")) + } + + fn get_display_by_user(user: &str) -> String { + // log::debug!("w {}", &user); + if let Ok(output) = std::process::Command::new("w").arg(&user).output() { + for line in String::from_utf8_lossy(&output.stdout).lines() { + let mut iter = line.split_whitespace(); + let b = iter.nth(2); + if let Some(b) = b { + if b.starts_with(":") { + return b.to_owned(); + } + } + } + } + // above not work for gdm user + //log::debug!("ls -l /tmp/.X11-unix/"); + let mut last = "".to_owned(); + if let Ok(output) = std::process::Command::new("ls") + .args(vec!["-l", "/tmp/.X11-unix/"]) + .output() + { + for line in String::from_utf8_lossy(&output.stdout).lines() { + let mut iter = line.split_whitespace(); + let user_field = iter.nth(2); + if let Some(x) = iter.last() { + if x.starts_with("X") { + last = x.replace("X", ":").to_owned(); + if user_field == Some(&user) { + return last; + } + } + } + } + } + last + } + + fn add_xauth_cookie( + file: &str, + display: &str, + uid: u32, + gid: u32, + envs: &HashMap<&str, String>, + ) -> ResultType<()> { + let randstr = (0..16) + .map(|_| format!("{:02x}", random::())) + .collect::(); + let output = Command::new("xauth") + .uid(uid) + .gid(gid) + .envs(envs) + .args(vec!["-q", "-f", file, "add", display, ".", &randstr]) + .output()?; + // xauth run success, even the following error occurs. + // Ok(Output { status: ExitStatus(unix_wait_status(0)), stdout: "", stderr: "xauth: file .Xauthority does not exist\n" }) + let errmsg = String::from_utf8_lossy(&output.stderr).to_string(); + if !errmsg.is_empty() { + if !errmsg.contains("does not exist") { + bail!("Failed to launch xauth, {}", errmsg) + } + } + Ok(()) + } + + fn wait_x_server_running(pid: u32, display_num: u32, max_wait_secs: u64) -> ResultType<()> { + let wait_begin = Instant::now(); + loop { + if run_cmds(format!("ls /proc/{}", pid))?.is_empty() { + bail!("X server exit"); + } + + if Self::is_x_server_running(display_num) { + return Ok(()); + } + if wait_begin.elapsed().as_secs() > max_wait_secs { + bail!("Failed to wait xserver after {} seconds", max_wait_secs); + } + std::thread::sleep(Duration::from_millis(300)); + } + } + + fn refresh(&mut self) -> ResultType { + *self = Self::new(); + if self.get_env_seat0()? || self.get_env_active()? { + self.get_display(); + self.get_xauth(); + Ok(true) + } else { + Ok(false) + } + } +} + +impl Drop for Desktop { + fn drop(&mut self) { + self.stop_children(); + } +} + +impl Desktop { + fn fatal_exit() { + std::process::exit(0); + } + + pub fn new() -> Self { + Self { + env: DesktopEnv::new(), + child_exit: Arc::new(AtomicBool::new(true)), + is_child_running: Arc::new(AtomicBool::new(false)), + } + } + + fn try_start_x_session(&mut self, username: &str, password: &str) -> ResultType<()> { + match get_user_by_name(username) { + Some(userinfo) => { + let mut client = pam::Client::with_password(pam_get_service_name())?; + client + .conversation_mut() + .set_credentials(username, password); + match client.authenticate() { + Ok(_) => { + if self.env.is_ready() && self.env.username == username { + return Ok(()); + } + + self.env.username = username.to_string(); + self.env.uid = userinfo.uid().to_string(); + self.env.protocal = Protocal::Unknown; + match self.start_x_session(&userinfo, password) { + Ok(_) => { + log::info!("Succeeded to start x11, update env {:?}", &self.env); + self.env.update_env(); + Ok(()) + } + Err(e) => { + self.env = DesktopEnv::new(); + self.env.update_env(); + bail!("failed to start x session, {}", e); + } + } + } + Err(e) => { + bail!("failed to check user pass for {}, {}", username, e); + } + } + } + None => { + bail!("failed to get userinfo of {}", username); + } + } + } + + fn start_x_session(&mut self, userinfo: &User, password: &str) -> ResultType<()> { + self.stop_children(); + + let display_num = DesktopEnv::get_avail_display()?; + // "xServer_ip:display_num.screen_num" + self.env.display = format!(":{}", display_num); + + let uid = userinfo.uid(); + let gid = userinfo.primary_group_id(); + let envs = HashMap::from([ + ("SHELL", userinfo.shell().to_string_lossy().to_string()), + ("PATH", "/sbin:/bin:/usr/bin:/usr/local/bin".to_owned()), + ("USER", self.env.username.clone()), + ("UID", userinfo.uid().to_string()), + ("HOME", userinfo.home_dir().to_string_lossy().to_string()), + ( + "XDG_RUNTIME_DIR", + format!("/run/user/{}", userinfo.uid().to_string()), + ), + // ("DISPLAY", self.display.clone()), + // ("XAUTHORITY", self.xauth.clone()), + // (ENV_DESKTOP_PROTOCAL, XProtocal::X11.to_string()), + ]); + let env = self.env.clone(); + self.child_exit.store(false, Ordering::SeqCst); + let is_child_running = self.is_child_running.clone(); + + let (tx_res, rx_res) = sync_channel(1); + let password = password.to_string(); + // start x11 + std::thread::spawn(move || { + match Self::start_x_session_thread( + tx_res.clone(), + is_child_running, + env, + uid, + gid, + display_num, + password, + envs, + ) { + Ok(_) => {} + Err(e) => { + log::error!("Failed to start x session thread"); + allow_err!(tx_res.send(format!("Failed to start x session thread, {}", e))); + } + } + }); + + // wait x11 + match rx_res.recv_timeout(Duration::from_millis(10_000)) { + Ok(res) => { + if res == "" { + self.env.protocal = Protocal::X11; + Ok(()) + } else { + bail!(res) + } + } + Err(e) => { + bail!("Failed to recv x11 result {}", e) + } + } + } + + fn start_x_session_thread( + tx_res: SyncSender, + is_child_running: Arc, + env: DesktopEnv, + uid: u32, + gid: u32, + display_num: u32, + password: String, + envs: HashMap<&str, String>, + ) -> ResultType<()> { + let mut client = pam::Client::with_password(pam_get_service_name())?; + client + .conversation_mut() + .set_credentials(&env.username, &password); + client.authenticate()?; + + client.set_item(pam::PamItemType::TTY, &env.display)?; + client.open_session()?; + + // fixme: FreeBSD kernel needs to login here. + // see: https://github.com/neutrinolabs/xrdp/blob/a64573b596b5fb07ca3a51590c5308d621f7214e/sesman/session.c#L556 + + let (child_xorg, child_wm) = Self::start_x11(&env, uid, gid, display_num, &envs)?; + is_child_running.store(true, Ordering::SeqCst); + + log::info!("Start xorg and wm done, notify and wait xtop x11"); + allow_err!(tx_res.send("".to_owned())); + + Self::wait_stop_x11(child_xorg, child_wm); + log::info!("Wait x11 stop done"); + Ok(()) + } + + fn wait_xorg_exit(child_xorg: &mut Child) -> ResultType { + if let Ok(_) = child_xorg.kill() { + for _ in 0..3 { + match child_xorg.try_wait() { + Ok(Some(status)) => return Ok(format!("Xorg exit with {}", status)), + Ok(None) => {} + Err(e) => { + // fatal error + log::error!("Failed to wait xorg process, {}", e); + bail!("Failed to wait xorg process, {}", e) + } + } + std::thread::sleep(std::time::Duration::from_millis(1_000)); + } + log::error!("Failed to wait xorg process, not exit"); + bail!("Failed to wait xorg process, not exit") + } else { + Ok("Xorg is already exited".to_owned()) + } + } + + fn start_x11( + env: &DesktopEnv, + uid: u32, + gid: u32, + display_num: u32, + envs: &HashMap<&str, String>, + ) -> ResultType<(Child, Child)> { + log::debug!("envs of user {}: {:?}", &env.username, &envs); + + DesktopEnv::add_xauth_cookie(&env.xauth, &env.display, uid, gid, &envs)?; + + // Start Xorg + let mut child_xorg = Self::start_x_server(&env.xauth, &env.display, uid, gid, &envs)?; + + log::info!("xorg started, wait 10 secs to ensuer x server is running"); + + let max_wait_secs = 10; + // wait x server running + if let Err(e) = + DesktopEnv::wait_x_server_running(child_xorg.id(), display_num, max_wait_secs) + { + match Self::wait_xorg_exit(&mut child_xorg) { + Ok(msg) => log::info!("{}", msg), + Err(e) => { + log::error!("{}", e); + Self::fatal_exit(); + } + } + bail!(e) + } + + log::info!( + "xorg is running, start x window manager with DISPLAY: {}, XAUTHORITY: {}", + &env.display, + &env.xauth + ); + + std::env::set_var("DISPLAY", &env.display); + std::env::set_var("XAUTHORITY", &env.xauth); + // start window manager (startwm.sh) + let child_wm = match Self::start_x_window_manager(uid, gid, &envs) { + Ok(c) => c, + Err(e) => { + match Self::wait_xorg_exit(&mut child_xorg) { + Ok(msg) => log::info!("{}", msg), + Err(e) => { + log::error!("{}", e); + Self::fatal_exit(); + } + } + bail!(e) + } + }; + log::info!("x window manager is started"); + + Ok((child_xorg, child_wm)) + } + + fn try_wait_x11_child_exit(child_xorg: &mut Child, child_wm: &mut Child) -> bool { + match child_xorg.try_wait() { + Ok(Some(status)) => { + println!( + "=============================MYDEBUG Xorg exit with {}", + status + ); + log::info!("Xorg exit with {}", status); + return true; + } + Ok(None) => {} + Err(e) => log::error!("Failed to wait xorg process, {}", e), + } + + match child_wm.try_wait() { + Ok(Some(status)) => { + println!( + "=============================MYDEBUG: wm exit with {}", + status + ); + log::info!("wm exit with {}", status); + return true; + } + Ok(None) => {} + Err(e) => log::error!("Failed to wait xorg process, {}", e), + } + false + } + + fn wait_x11_children_exit(child_xorg: &mut Child, child_wm: &mut Child) { + log::debug!("Try kill child process xorg"); + if let Ok(_) = child_xorg.kill() { + let mut exited = false; + for _ in 0..2 { + match child_xorg.try_wait() { + Ok(Some(status)) => { + println!( + "=============================MYDEBUG Xorg exit with {}", + status + ); + log::info!("Xorg exit with {}", status); + exited = true; + break; + } + Ok(None) => {} + Err(e) => { + println!( + "=============================MYDEBUG Failed to wait xorg process, {}", + &e + ); + log::error!("Failed to wait xorg process, {}", e); + Self::fatal_exit(); + } + } + std::thread::sleep(std::time::Duration::from_millis(1_000)); + } + if !exited { + println!( + "=============================MYDEBUG Failed to wait child xorg, after kill()" + ); + log::error!("Failed to wait child xorg, after kill()"); + // try kill -9? + } + } + log::debug!("Try kill child process wm"); + if let Ok(_) = child_wm.kill() { + let mut exited = false; + for _ in 0..2 { + match child_wm.try_wait() { + Ok(Some(status)) => { + println!( + "=============================MYDEBUG wm exit with {}", + status + ); + log::info!("wm exit with {}", status); + exited = true; + } + Ok(None) => {} + Err(e) => { + println!( + "=============================MYDEBUG Failed to wait wm process, {}", + &e + ); + log::error!("Failed to wait wm process, {}", e); + Self::fatal_exit(); + } + } + std::thread::sleep(std::time::Duration::from_millis(1_000)); + } + if !exited { + println!( + "=============================MYDEBUG Failed to wait child xorg, after kill()" + ); + log::error!("Failed to wait child xorg, after kill()"); + // try kill -9? + } + } + } + + fn try_wait_stop_x11(child_xorg: &mut Child, child_wm: &mut Child) -> bool { + let mut inst = DESKTOP_INST.lock().unwrap(); + let mut exited = true; + if let Some(inst) = &mut (*inst) { + if inst.child_exit.load(Ordering::SeqCst) { + exited = true; + } else { + exited = Self::try_wait_x11_child_exit(child_xorg, child_wm); + } + if exited { + println!("=============================MYDEBUG begin to wait x11 children exit"); + Self::wait_x11_children_exit(child_xorg, child_wm); + inst.is_child_running.store(false, Ordering::SeqCst); + inst.child_exit.store(true, Ordering::SeqCst); + } + } + exited + } + + fn wait_stop_x11(mut child_xorg: Child, mut child_wm: Child) { + loop { + if Self::try_wait_stop_x11(&mut child_xorg, &mut child_wm) { + break; + } + std::thread::sleep(Duration::from_millis(super::SERVICE_INTERVAL)); + } + } + + fn get_xorg() -> ResultType<&'static str> { + // Fedora 26 or later + let xorg = "/usr/libexec/Xorg"; + if Path::new(xorg).is_file() { + return Ok(xorg); + } + // Debian 9 or later + let xorg = "/usr/lib/xorg/Xorg"; + if Path::new(xorg).is_file() { + return Ok(xorg); + } + // Ubuntu 16.04 or later + let xorg = "/usr/lib/xorg/Xorg"; + if Path::new(xorg).is_file() { + return Ok(xorg); + } + // Arch Linux + let xorg = "/usr/lib/xorg-server/Xorg"; + if Path::new(xorg).is_file() { + return Ok(xorg); + } + // Arch Linux + let xorg = "/usr/lib/Xorg"; + if Path::new(xorg).is_file() { + return Ok(xorg); + } + // CentOS 7 /usr/bin/Xorg or param=Xorg + + log::warn!("Failed to find xorg, use default Xorg.\n Please add \"allowed_users=anybody\" to \"/etc/X11/Xwrapper.config\"."); + Ok("Xorg") + } + + fn start_x_server( + xauth: &str, + display: &str, + uid: u32, + gid: u32, + envs: &HashMap<&str, String>, + ) -> ResultType { + let xorg = Self::get_xorg()?; + log::info!("Use xorg: {}", &xorg); + match Command::new(xorg) + .envs(envs) + .uid(uid) + .gid(gid) + .args(vec![ + "-noreset", + "+extension", + "GLX", + "+extension", + "RANDR", + "+extension", + "RENDER", + //"-logfile", + //"/tmp/RustDesk_xorg.log", + "-config", + "rustdesk/xorg.conf", + "-auth", + xauth, + display, + ]) + .spawn() + { + Ok(c) => Ok(c), + Err(e) => { + bail!("Failed to start Xorg with display {}, {}", display, e); + } + } + } + + fn start_x_window_manager( + uid: u32, + gid: u32, + envs: &HashMap<&str, String>, + ) -> ResultType { + match Command::new("/etc/rustdesk/startwm.sh") + .envs(envs) + .uid(uid) + .gid(gid) + .spawn() + { + Ok(c) => Ok(c), + Err(e) => { + bail!("Failed to start window manager, {}", e); + } + } + } + + fn stop_children(&mut self) { + self.child_exit.store(true, Ordering::SeqCst); + for _i in 1..10 { + if !self.is_child_running.load(Ordering::SeqCst) { + break; + } + std::thread::sleep(Duration::from_millis(super::SERVICE_INTERVAL)); + } + if self.is_child_running.load(Ordering::SeqCst) { + log::warn!("xdesktop child is still running!"); + } + } +} + +fn pam_get_service_name() -> &'static str { + if Path::new("/etc/pam.d/rustdesk").is_file() { + "rustdesk" + } else { + "gdm" + } +} + +impl ToString for Protocal { + fn to_string(&self) -> String { + match self { + Protocal::X11 => ENV_DESKTOP_PROTOCAL__X11.to_owned(), + Protocal::Wayland => ENV_DESKTOP_PROTOCAL_WAYLAND.to_owned(), + Protocal::Unknown => ENV_DESKTOP_PROTOCAL_UNKNOWN.to_owned(), + } + } +} diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 777de3b01..16bcc775a 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -17,6 +17,9 @@ pub mod delegate; #[cfg(target_os = "linux")] pub mod linux; +#[cfg(target_os = "linux")] +pub mod linux_desktop; + #[cfg(not(any(target_os = "android", target_os = "ios")))] use hbb_common::{message_proto::CursorData, ResultType}; #[cfg(not(any(target_os = "macos", target_os = "android", target_os = "ios")))] diff --git a/src/port_forward.rs b/src/port_forward.rs index f50f40db8..4e05ad92f 100644 --- a/src/port_forward.rs +++ b/src/port_forward.rs @@ -199,8 +199,8 @@ async fn connect_and_login_2( }, d = ui_receiver.recv() => { match d { - Some(Data::Login((password, remember))) => { - interface.handle_login_from_ui(password, remember, &mut stream).await; + Some(Data::Login((os_username, os_password, password, remember))) => { + interface.handle_login_from_ui(os_username, os_password, password, remember, &mut stream).await; } _ => {} } diff --git a/src/rendezvous_mediator.rs b/src/rendezvous_mediator.rs index 8b7dae1ba..339a45bf3 100644 --- a/src/rendezvous_mediator.rs +++ b/src/rendezvous_mediator.rs @@ -54,6 +54,8 @@ impl RendezvousMediator { pub async fn start_all() { let mut nat_tested = false; check_zombie(); + #[cfg(target_os = "linux")] + crate::server::check_xdesktop(); let server = new_server(); if Config::get_nat_type() == NatType::UNKNOWN_NAT as i32 { crate::test_nat_type(); @@ -72,7 +74,10 @@ impl RendezvousMediator { allow_err!(super::lan::start_listening()); }); } - loop { + #[cfg(target_os = "linux")] + crate::platform::linux_desktop::start_xdesktop(); + SHOULD_EXIT.store(false, Ordering::SeqCst); + while !SHOULD_EXIT.load(Ordering::SeqCst) { Config::reset_online(); if Config::get_option("stop-service").is_empty() { if !nat_tested { @@ -96,6 +101,8 @@ impl RendezvousMediator { } sleep(1.).await; } + #[cfg(target_os = "linux")] + crate::platform::linux_desktop::stop_xdesktop(); } pub async fn start(server: ServerPtr, host: String) -> ResultType<()> { diff --git a/src/server.rs b/src/server.rs index 681e7bed1..81c967ba1 100644 --- a/src/server.rs +++ b/src/server.rs @@ -356,6 +356,40 @@ pub fn check_zombie() { }); } +#[cfg(target_os = "linux")] +pub fn check_xdesktop() { + std::thread::spawn(|| { + use crate::platform::linux_desktop::{get_desktop_env, DesktopEnv}; + let mut desktop_env = DesktopEnv::new(); + loop { + let new_env = match get_desktop_env() { + Some(env) => env, + None => DesktopEnv::new(), + }; + + if new_env.is_ready() { + if !desktop_env.is_ready() { + desktop_env = new_env.clone(); + } else { + if !desktop_env.is_same_env(&new_env) { + log::info!("xdesktop env diff {:?} to {:?}", &new_env, desktop_env); + break; + } + } + } else { + if desktop_env.is_ready() { + log::info!("xdesktop env diff {:?} to {:?}", &new_env, desktop_env); + break; + } + } + + std::thread::sleep(Duration::from_millis(300)); + } + + crate::RendezvousMediator::restart(); + }); +} + /// Start the host server that allows the remote peer to control the current machine. /// /// # Arguments diff --git a/src/server/connection.rs b/src/server/connection.rs index 23e166fcf..36190485e 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -57,6 +57,16 @@ lazy_static::lazy_static! { pub static CLICK_TIME: AtomicI64 = AtomicI64::new(0); pub static MOUSE_MOVE_TIME: AtomicI64 = AtomicI64::new(0); +pub const LOGIN_MSG_XDESKTOP_NOT_INITED: &str = "xdesktop env is not inited"; +pub const LOGIN_MSG_XSESSION_NOT_READY: &str = "xsession unready"; +pub const LOGIN_MSG_XSESSION_FAILED: &str = "xsession failed"; +pub const LOGIN_MSG_XSESSION_ANOTHER_USER_READTY: &str = "xsession another user login"; +pub const LOGIN_MSG_XSESSION_NOT_READY_PASSWORD_EMPTY: &str = "xsession unready, password empty"; +pub const LOGIN_MSG_XSESSION_NOT_READY_PASSWORD_WRONG: &str = "xsession unready, password wrong"; +pub const LOGIN_MSG_PASSWORD_EMPTY: &str = "Empty Password"; +pub const LOGIN_MSG_PASSWORD_WRONG: &str = "Wrong Password"; +pub const LOGIN_MSG_OFFLINE: &str = "Offline"; + #[derive(Clone, Default)] pub struct ConnInner { id: i32, @@ -902,7 +912,7 @@ impl Connection { } #[cfg(not(any(target_os = "android", target_os = "ios")))] if self.file_transfer.is_some() { - if crate::platform::is_prelogin() || self.tx_to_cm.send(ipc::Data::Test).is_err() { + if !crate::platform::is_prelogin() || self.tx_to_cm.send(ipc::Data::Test).is_err() { username = "".to_owned(); } } @@ -1061,6 +1071,47 @@ impl Connection { self.tx_input.send(MessageInput::Key((msg, press))).ok(); } + fn try_start_desktop(_username: &str, _passsword: &str) -> String { + #[cfg(target_os = "linux")] + { + if _username.is_empty() { + match crate::platform::linux_desktop::get_desktop_env() { + Some(desktop_env) => { + if desktop_env.is_ready() { + "" + } else { + LOGIN_MSG_XSESSION_NOT_READY + } + } + None => LOGIN_MSG_XDESKTOP_NOT_INITED, + } + .to_owned() + } else { + match crate::platform::linux_desktop::try_start_x_session(_username, _passsword) { + Ok(desktop_env) => { + if desktop_env.is_ready() { + if _username != desktop_env.username { + LOGIN_MSG_XSESSION_ANOTHER_USER_READTY.to_owned() + } else { + "".to_owned() + } + } else { + LOGIN_MSG_XSESSION_NOT_READY.to_owned() + } + } + Err(e) => { + log::error!("Failed to start xsession {}", e); + LOGIN_MSG_XSESSION_FAILED.to_owned() + } + } + } + } + #[cfg(not(target_os = "linux"))] + { + "".to_owned() + } + } + fn validate_one_password(&self, password: String) -> bool { if password.len() == 0 { return false; @@ -1225,16 +1276,31 @@ impl Connection { } _ => {} } + + let desktop_err = match lr.os_login.as_ref() { + Some(os_login) => Self::try_start_desktop(&os_login.username, &os_login.password), + None => Self::try_start_desktop("", ""), + }; + if !desktop_err.is_empty() && desktop_err != LOGIN_MSG_XSESSION_NOT_READY { + self.send_login_error(desktop_err).await; + return true; + } + if !hbb_common::is_ipv4_str(&lr.username) && lr.username != Config::get_id() { - self.send_login_error("Offline").await; + self.send_login_error(LOGIN_MSG_OFFLINE).await; } else if password::approve_mode() == ApproveMode::Click || password::approve_mode() == ApproveMode::Both && !password::has_valid_password() { - self.try_start_cm(lr.my_id, lr.my_name, false); - if hbb_common::get_version_number(&lr.version) - >= hbb_common::get_version_number("1.2.0") - { - self.send_login_error("No Password Access").await; + if desktop_err.is_empty() { + self.try_start_cm(lr.my_id, lr.my_name, false); + if hbb_common::get_version_number(&lr.version) + >= hbb_common::get_version_number("1.2.0") + { + self.send_login_error("No Password Access").await; + } + } else { + self.send_login_error(LOGIN_MSG_XSESSION_NOT_READY_PASSWORD_EMPTY) + .await; } return true; } else if password::approve_mode() == ApproveMode::Password @@ -1290,16 +1356,25 @@ impl Connection { .lock() .unwrap() .insert(self.ip.clone(), failure); - self.send_login_error("Wrong Password").await; - self.try_start_cm(lr.my_id, lr.my_name, false); + if desktop_err.is_empty() { + self.send_login_error(LOGIN_MSG_PASSWORD_WRONG).await; + self.try_start_cm(lr.my_id, lr.my_name, false); + } else { + self.send_login_error(LOGIN_MSG_XSESSION_NOT_READY_PASSWORD_WRONG) + .await; + } } else { if failure.0 != 0 { LOGIN_FAILURES.lock().unwrap().remove(&self.ip); } - self.try_start_cm(lr.my_id, lr.my_name, true); - self.send_logon_response().await; - if self.port_forward_socket.is_some() { - return false; + if desktop_err.is_empty() { + self.send_logon_response().await; + self.try_start_cm(lr.my_id, lr.my_name, true); + if self.port_forward_socket.is_some() { + return false; + } + } else { + self.send_login_error(desktop_err).await; } } } @@ -2072,7 +2147,7 @@ async fn start_ipc( tx_from_cm: mpsc::UnboundedSender, ) -> ResultType<()> { loop { - if !crate::platform::is_prelogin() { + if crate::platform::is_prelogin() { break; } sleep(1.).await; diff --git a/src/server/service.rs b/src/server/service.rs index 9cc1e860c..fe038f3c0 100644 --- a/src/server/service.rs +++ b/src/server/service.rs @@ -221,6 +221,7 @@ impl> ServiceTmpl { thread::sleep(interval - elapsed); } } + log::info!("Service {} exit", sp.name()); }); self.0.write().unwrap().handle = Some(thread); } @@ -256,6 +257,7 @@ impl> ServiceTmpl { } thread::sleep(time::Duration::from_millis(HIBERNATE_TIMEOUT)); } + log::info!("Service {} exit", sp.name()); }); self.0.write().unwrap().handle = Some(thread); } diff --git a/src/ui/common.css b/src/ui/common.css index 0fb9afcb1..9845ff104 100644 --- a/src/ui/common.css +++ b/src/ui/common.css @@ -142,6 +142,10 @@ div.password input { font-size: 1.2em; } +div.username input { + font-size: 1.2em; +} + svg { background: none; } diff --git a/src/ui/common.tis b/src/ui/common.tis index b6723b131..ef6d215aa 100644 --- a/src/ui/common.tis +++ b/src/ui/common.tis @@ -252,7 +252,7 @@ function msgbox(type, title, content, link="", callback=null, height=180, width= view.close(); return; } - handler.login(res.password, res.remember); + handler.login("", "", res.password, res.remember); if (!is_port_forward) { // Specially handling file transfer for no permission hanging issue (including 60ms // timer in setPermission. @@ -262,6 +262,30 @@ function msgbox(type, title, content, link="", callback=null, height=180, width= else msgbox("connecting", "Connecting...", "Logging in..."); } }; + } else if (type == "xsession-login" || type == "xsession-re-login") { + callback = function (res) { + if (!res) { + view.close(); + return; + } + handler.login(res.osusername, res.ospassword, "", false); + if (!is_port_forward) { + if (is_file_transfer) handler.msgbox("connecting", "Connecting...", "Logging in..."); + else msgbox("connecting", "Connecting...", "Logging in..."); + } + }; + } else if (type.indexOf("xsession-login") >= 0) { + callback = function (res) { + if (!res) { + view.close(); + return; + } + handler.login(res.osusername, res.ospassword, res.password, res.remember); + if (!is_port_forward) { + if (is_file_transfer) handler.msgbox("connecting", "Connecting...", "Logging in..."); + else msgbox("connecting", "Connecting...", "Logging in..."); + } + }; } else if (type.indexOf("custom") < 0 && !is_port_forward && !callback) { callback = function() { view.close(); } } else if (type == 'wait-remote-accept-nook') { diff --git a/src/ui/msgbox.tis b/src/ui/msgbox.tis index d5c60d95c..d9a311452 100644 --- a/src/ui/msgbox.tis +++ b/src/ui/msgbox.tis @@ -32,7 +32,7 @@ class MsgboxComponent: Reactor.Component { } function getIcon(color) { - if (this.type == "input-password") { + if (this.type == "input-password" || this.type == "xsession-login" || this.type == "xsession-login-password") { return ; } if (this.type == "connecting") { @@ -41,7 +41,7 @@ class MsgboxComponent: Reactor.Component { if (this.type == "success") { return ; } - if (this.type.indexOf("error") >= 0 || this.type == "re-input-password") { + if (this.type.indexOf("error") >= 0 || this.type == "re-input-password" || this.type == "xsession-re-login" || this.type == "xsession-login-re-password") { return ; } return null; @@ -56,11 +56,36 @@ class MsgboxComponent: Reactor.Component { ; } + function getInputUserPasswordContent() { + return
+
{translate("OS Username")}
+
+
{translate("OS Password")}
+ +
+
; + } + + function getXsessionPasswordContent() { + return
+
{translate("OS Username")}
+
+
{translate("OS Password")}
+ +
{translate('Please enter your password')}
+ +
{translate('Remember password')}
+
; + } + function getContent() { if (this.type == "input-password") { return this.getInputPasswordContent(); - } - if (this.type == "custom-os-password") { + } else if (this.type == "xsession-login") { + return this.getInputUserPasswordContent(); + } else if (this.type == "xsession-login-password") { + return this.getXsessionPasswordContent(); + } else if (this.type == "custom-os-password") { var ts = this.auto_login ? { checked: true } : {}; return
@@ -71,13 +96,13 @@ class MsgboxComponent: Reactor.Component { } function getColor() { - if (this.type == "input-password" || this.type == "custom-os-password") { + if (this.type == "input-password" || this.type == "custom-os-password" || this.type == "xsession-login" || this.type == "xsession-login-password") { return "#AD448E"; } if (this.type == "success") { return "#32bea6"; } - if (this.type.indexOf("error") >= 0 || this.type == "re-input-password") { + if (this.type.indexOf("error") >= 0 || this.type == "re-input-password" || this.type == "xsession-re-login" || this.type == "xsession-login-re-password") { return "#e04f5f"; } return "#2C8CFF"; @@ -177,6 +202,16 @@ class MsgboxComponent: Reactor.Component { this.update(); return; } + if (this.type == "xsession-re-login") { + this.type = "xsession-login"; + this.update(); + return; + } + if (this.type == "xsession-login-re-password") { + this.type = "xsession-login-password"; + this.update(); + return; + } var values = this.getValues(); if (this.callback) { var self = this; @@ -238,6 +273,21 @@ class MsgboxComponent: Reactor.Component { return; } } + if (this.type == "xsession-login") { + values.osusername = (values.osusername || "").trim(); + values.ospassword = (values.ospassword || "").trim(); + if (!values.osusername || !values.ospassword) { + return; + } + } + if (this.type == "xsession-login-password") { + values.password = (values.password || "").trim(); + values.osusername = (values.osusername || "").trim(); + values.ospassword = (values.ospassword || "").trim(); + if (!values.osusername || !values.ospassword || !values.password) { + return; + } + } return values; } diff --git a/src/ui/remote.rs b/src/ui/remote.rs index 68decf955..966315a23 100644 --- a/src/ui/remote.rs +++ b/src/ui/remote.rs @@ -398,7 +398,7 @@ impl sciter::EventHandler for SciterSession { fn is_file_transfer(); fn is_port_forward(); fn is_rdp(); - fn login(String, bool); + fn login(String, String, String, bool); fn new_rdp(); fn send_mouse(i32, i32, i32, bool, bool, bool, bool); fn enter(); diff --git a/src/ui_session_interface.rs b/src/ui_session_interface.rs index f89be4879..2a0776668 100644 --- a/src/ui_session_interface.rs +++ b/src/ui_session_interface.rs @@ -711,8 +711,14 @@ impl Session { fs::get_string(&path) } - pub fn login(&self, password: String, remember: bool) { - self.send(Data::Login((password, remember))); + pub fn login( + &mut self, + os_username: String, + os_password: String, + password: String, + remember: bool, + ) { + self.send(Data::Login((os_username, os_password, password, remember))); } pub fn new_rdp(&self) { @@ -1005,8 +1011,23 @@ impl Interface for Session { handle_hash(self.lc.clone(), pass, hash, self, peer).await; } - async fn handle_login_from_ui(&mut self, password: String, remember: bool, peer: &mut Stream) { - handle_login_from_ui(self.lc.clone(), password, remember, peer).await; + async fn handle_login_from_ui( + &mut self, + os_username: String, + os_password: String, + password: String, + remember: bool, + peer: &mut Stream, + ) { + handle_login_from_ui( + self.lc.clone(), + os_username, + os_password, + password, + remember, + peer, + ) + .await; } async fn handle_test_delay(&mut self, t: TestDelay, peer: &mut Stream) {