linux virtual display, init commit

Signed-off-by: fufesou <shuanglongchen@yeah.net>
This commit is contained in:
fufesou 2023-03-22 17:01:11 +08:00
parent ddd0d6eafc
commit 10eddc139c
24 changed files with 1544 additions and 54 deletions

54
Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -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; }

View File

@ -191,7 +191,7 @@ pub fn run_cmds(cmds: &str) -> ResultType<String> {
}
#[cfg(not(feature = "flatpak"))]
fn run_loginctl(args: Option<Vec<&str>>) -> std::io::Result<std::process::Output> {
pub(super) fn run_loginctl(args: Option<Vec<&str>>) -> std::io::Result<std::process::Output> {
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<Vec<&str>>) -> std::io::Result<std::process::Output
}
#[cfg(feature = "flatpak")]
fn run_loginctl(args: Option<Vec<&str>>) -> std::io::Result<std::process::Output> {
pub(super) fn run_loginctl(args: Option<Vec<&str>>) -> std::io::Result<std::process::Output> {
let mut l_args = String::from("loginctl");
if let Some(a) = args {
l_args = format!("{} {}", l_args, a.join(" "));

View File

@ -0,0 +1,5 @@
#%PAM-1.0
@include common-auth
@include common-account
@include common-session
@include common-password

5
res/pam.d/rustdesk.suse Normal file
View File

@ -0,0 +1,5 @@
#%PAM-1.0
auth include common-auth
account include common-account
session include common-session
password include common-password

130
res/startwm.sh Executable file
View File

@ -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

30
res/xorg.conf Normal file
View File

@ -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"
SubSectionSub "Display"
Depth 24
Modes "1920x1080" "1280x720"
EndSubSection
EndSection

View File

@ -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) {

View File

@ -1591,7 +1591,12 @@ impl LoginConfigHandler {
}
/// Create a [`Message`] for login.
fn create_login_msg(&self, password: Vec<u8>) -> Message {
fn create_login_msg(
&self,
os_username: String,
os_password: String,
password: Vec<u8>,
) -> 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<RwLock<LoginConfigHandler>>,
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<RwLock<LoginConfigHandler>>, password: Vec<u8>, peer: &mut Stream) {
let msg_out = lc.read().unwrap().create_login_msg(password);
async fn send_login(
lc: Arc<RwLock<LoginConfigHandler>>,
os_username: String,
os_password: String,
password: Vec<u8>,
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<RwLock<LoginConfigHandler>>, password: Vec<u8>, 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<RwLock<LoginConfigHandler>>,
os_username: String,
os_password: String,
password: String,
remember: bool,
peer: &mut Stream,
) {
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<RwLock<LoginConfigHandler>>;
@ -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)),

View File

@ -360,9 +360,9 @@ impl<T: InvokeUiSession> Remote<T> {
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 => {

View File

@ -1,3 +1,4 @@
use super::linux_desktop::GNOME_SESSION_BINARY;
use super::{CursorData, ResultType};
use desktop::Desktop;
pub use hbb_common::platform::linux::*;

View File

@ -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<AtomicBool> = Arc::new(AtomicBool::new(false));
static ref DESKTOP_INST: Arc<Mutex<Option<Desktop>>> = 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<AtomicBool>,
is_child_running: Arc<AtomicBool>,
}
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<DesktopEnv> {
match &*DESKTOP_INST.lock().unwrap() {
Some(inst) => Some(inst.env.clone()),
None => None,
}
}
pub fn try_start_x_session(username: &str, password: &str) -> ResultType<DesktopEnv> {
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<u32> {
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<bool> {
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<bool> {
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<bool> {
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::<u8>()))
.collect::<String>();
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<bool> {
*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<String>,
is_child_running: Arc<AtomicBool>,
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<String> {
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<Child> {
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<Child> {
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(),
}
}
}

View File

@ -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")))]

View File

@ -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;
}
_ => {}
}

View File

@ -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<()> {

View File

@ -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

View File

@ -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,17 +1276,32 @@ 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()
{
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
&& !password::has_valid_password()
@ -1290,17 +1356,26 @@ impl Connection {
.lock()
.unwrap()
.insert(self.ip.clone(), failure);
self.send_login_error("Wrong Password").await;
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);
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;
}
}
}
} else if let Some(message::Union::TestDelay(t)) = msg.union {
@ -2072,7 +2147,7 @@ async fn start_ipc(
tx_from_cm: mpsc::UnboundedSender<ipc::Data>,
) -> ResultType<()> {
loop {
if !crate::platform::is_prelogin() {
if crate::platform::is_prelogin() {
break;
}
sleep(1.).await;

View File

@ -221,6 +221,7 @@ impl<T: Subscriber + From<ConnInner>> ServiceTmpl<T> {
thread::sleep(interval - elapsed);
}
}
log::info!("Service {} exit", sp.name());
});
self.0.write().unwrap().handle = Some(thread);
}
@ -256,6 +257,7 @@ impl<T: Subscriber + From<ConnInner>> ServiceTmpl<T> {
}
thread::sleep(time::Duration::from_millis(HIBERNATE_TIMEOUT));
}
log::info!("Service {} exit", sp.name());
});
self.0.write().unwrap().handle = Some(thread);
}

View File

@ -142,6 +142,10 @@ div.password input {
font-size: 1.2em;
}
div.username input {
font-size: 1.2em;
}
svg {
background: none;
}

View File

@ -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') {

View File

@ -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 <svg viewBox="0 0 505 505"><circle cx="252.5" cy="252.5" r="252.5" fill={color}/><path d="M271.9 246.1c29.2 17.5 67.6 13.6 92.7-11.5 29.7-29.7 29.7-77.8 0-107.4s-77.8-29.7-107.4 0c-25.1 25.1-29 63.5-11.5 92.7L118.1 347.4l26.2 26.2 26.4 26.4 10.6-10.6-10.1-10.1 9.7-9.7 10.1 10.1 10.6-10.6-10.1-10 9.7-9.7 10.1 10.1 10.6-10.6-26.4-26.3 76.4-76.5z" fill="#fff"/><circle cx="337.4" cy="154.4" r="17.7" fill={color}/></svg>;
}
if (this.type == "connecting") {
@ -41,7 +41,7 @@ class MsgboxComponent: Reactor.Component {
if (this.type == "success") {
return <svg viewBox="0 0 512 512"><circle cx="256" cy="256" r="256" fill={color} /><path fill="#fff" d="M235.472 392.08l-121.04-94.296 34.416-44.168 74.328 57.904 122.672-177.016 46.032 31.888z"/></svg>;
}
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 <svg viewBox="0 0 512 512"><ellipse cx="256" cy="256" rx="256" ry="255.832" fill={color}/><g fill="#fff"><path d="M376.812 337.18l-39.592 39.593-201.998-201.999 39.592-39.592z"/><path d="M376.818 174.825L174.819 376.824l-39.592-39.592 201.999-201.999z"/></g></svg>;
}
return null;
@ -56,11 +56,36 @@ class MsgboxComponent: Reactor.Component {
</div>;
}
function getInputUserPasswordContent() {
return <div .form>
<div>{translate("OS Username")}</div>
<div .username><input name='osusername' type='text' .outline-focus /></div>
<div>{translate("OS Password")}</div>
<PasswordComponent name='ospassword' />
<div></div>
</div>;
}
function getXsessionPasswordContent() {
return <div .form>
<div>{translate("OS Username")}</div>
<div .username><input name='osusername' type='text' .outline-focus /></div>
<div>{translate("OS Password")}</div>
<PasswordComponent name='ospassword' />
<div>{translate('Please enter your password')}</div>
<PasswordComponent />
<div><button|checkbox(remember) {ts}>{translate('Remember password')}</button></div>
</div>;
}
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 <div .form>
<PasswordComponent value={this.content} />
@ -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;
}

View File

@ -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();

View File

@ -711,8 +711,14 @@ impl<T: InvokeUiSession> Session<T> {
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<T: InvokeUiSession> Interface for Session<T> {
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) {