From 3b282c440a2b2599da8c6cc6a62133a10c4a5513 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Lureau?= Date: Tue, 8 Oct 2024 12:00:35 +0400 Subject: [PATCH] rdp: use hybrid + credentials support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Credentials must be set with dbus, ex: busctl --user call org.QemuDisplay /org/qemu_display/rdp org.QemuDisplay.RDP SetCredentials sss user pass '' Signed-off-by: Marc-André Lureau --- Cargo.lock | 3 ++ qemu-rdp/Cargo.toml | 3 ++ qemu-rdp/src/args.rs | 35 ++++-------------- qemu-rdp/src/server/mod.rs | 72 ++++++++++++++++++++------------------ 4 files changed, 49 insertions(+), 64 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9b2288c..eb787e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2164,6 +2164,7 @@ version = "0.5.0" source = "git+https://github.com/Devolutions/IronRDP#3c503cb2d1805a4164b668284e656c07437623b4" dependencies = [ "ironrdp-cliprdr", + "ironrdp-connector", "ironrdp-core", "ironrdp-displaycontrol", "ironrdp-pdu", @@ -3375,6 +3376,7 @@ dependencies = [ "async-trait", "bytes", "clap 4.5.20", + "enumflags2", "futures-util", "ironrdp", "keycodemap", @@ -3384,6 +3386,7 @@ dependencies = [ "tokio", "tracing", "tracing-subscriber", + "zbus", ] [[package]] diff --git a/qemu-rdp/Cargo.toml b/qemu-rdp/Cargo.toml index df523a2..94fc687 100644 --- a/qemu-rdp/Cargo.toml +++ b/qemu-rdp/Cargo.toml @@ -22,6 +22,7 @@ anyhow = "1.0" clap = { version = "4.5", features = ["derive", "cargo"] } async-trait = "0.1" ironrdp = { git = "https://github.com/Devolutions/IronRDP", features = [ + "connector", "server", "svc", "cliprdr", @@ -29,3 +30,5 @@ ironrdp = { git = "https://github.com/Devolutions/IronRDP", features = [ "rdpsnd", ] } futures-util = "0.3" +zbus.workspace = true +enumflags2 = "0.7.10" diff --git a/qemu-rdp/src/args.rs b/qemu-rdp/src/args.rs index 97f5b21..43a9dd2 100644 --- a/qemu-rdp/src/args.rs +++ b/qemu-rdp/src/args.rs @@ -1,43 +1,20 @@ -use clap::{clap_derive::ValueEnum, Parser}; +use std::path::PathBuf; -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] -pub enum SecurityProtocol { - Ssl, - Hybrid, - HybridEx, -} - -impl From for ironrdp::pdu::nego::SecurityProtocol { - fn from(value: SecurityProtocol) -> Self { - match value { - SecurityProtocol::Ssl => ironrdp::pdu::nego::SecurityProtocol::SSL, - SecurityProtocol::Hybrid => ironrdp::pdu::nego::SecurityProtocol::HYBRID, - SecurityProtocol::HybridEx => ironrdp::pdu::nego::SecurityProtocol::HYBRID_EX, - } - } -} +use clap::Parser; #[derive(Parser, Debug)] pub struct ServerArgs { /// IP address - #[clap(short, long, default_value = "127.0.0.1")] - pub address: std::net::IpAddr, - - /// IP port - #[clap(short, long, default_value = "3389")] - pub port: u16, - - /// Specify the security protocols to use - #[clap(long, value_enum, value_parser, default_value_t = SecurityProtocol::Ssl)] - pub security_protocol: SecurityProtocol, + #[clap(short, long, default_value = "0.0.0.0:3389")] + pub bind_addr: std::net::SocketAddr, /// Path to tls certificate #[clap(short, long, value_parser)] - pub cert: Option, + pub cert: PathBuf, /// Path to private key #[clap(short, long, value_parser)] - pub key: Option, + pub key: PathBuf, } #[derive(Parser, Debug)] diff --git a/qemu-rdp/src/server/mod.rs b/qemu-rdp/src/server/mod.rs index 0ff16dd..f847d82 100644 --- a/qemu-rdp/src/server/mod.rs +++ b/qemu-rdp/src/server/mod.rs @@ -3,15 +3,12 @@ mod display; mod input; mod sound; -use anyhow::{anyhow, Context, Error}; -use ironrdp::server::{ - tokio_rustls::{rustls, TlsAcceptor}, - ServerEvent, -}; +use anyhow::Error; +use enumflags2::BitFlags; +use ironrdp::server::{Credentials, ServerEvent, TlsIdentityCtx}; use qemu_display::{zbus, Display}; -use rustls_pemfile::{certs, pkcs8_private_keys}; -use std::{fs::File, io::BufReader, sync::Arc}; +use tokio::sync::mpsc::UnboundedSender; use tracing::debug; use ironrdp::server::RdpServer; @@ -28,20 +25,16 @@ pub struct Server { args: ServerArgs, } +struct DBusCtrl { + ev: UnboundedSender, +} + impl Server { pub fn new(dbus: zbus::Connection, args: ServerArgs) -> Self { Self { dbus, args } } pub async fn run(&mut self) -> Result<(), Error> { - let tls = self - .args - .cert - .as_ref() - .zip(self.args.key.as_ref()) - .map(|(cert, key)| acceptor(cert, key).unwrap()) - .ok_or_else(|| anyhow!("Failed to setup TLS"))?; - let dbus_display = Display::new::<()>(&self.dbus, None).await?; let handler = InputHandler::connect(&dbus_display).await?; @@ -55,9 +48,11 @@ impl Server { } }; + let tls = + TlsIdentityCtx::init_from_paths(self.args.cert.as_path(), self.args.key.as_path())?; let mut server = RdpServer::builder() - .with_addr((self.args.address, self.args.port)) - .with_tls(tls) + .with_addr(self.args.bind_addr) + .with_hybrid(tls.make_acceptor()?, tls.pub_key) .with_input_handler(handler) .with_display_handler(display) .with_cliprdr_factory(Some(Box::new(clipboard))) @@ -74,26 +69,33 @@ impl Server { ev.send(ServerEvent::Quit("org.qemu is gone".to_owned())) .unwrap(); }); + + let ev = server.event_sender().clone(); + self.dbus + .object_server() + .at("/org/qemu_display/rdp", DBusCtrl { ev }) + .await?; + self.dbus + .request_name_with_flags("org.QemuDisplay", BitFlags::EMPTY) + .await?; + server.run().await } } -fn acceptor(cert_path: &str, key_path: &str) -> Result { - let cert = certs(&mut BufReader::new(File::open(cert_path)?)) - .next() - .context("no certificate")??; - let key = pkcs8_private_keys(&mut BufReader::new(File::open(key_path)?)) - .next() - .context("no private key")? - .map(rustls::pki_types::PrivateKeyDer::from)?; - - let mut server_config = rustls::ServerConfig::builder() - .with_no_client_auth() - .with_single_cert(vec![cert], key) - .expect("bad certificate/key"); - - // This adds support for the SSLKEYLOGFILE env variable (https://wiki.wireshark.org/TLS#using-the-pre-master-secret) - server_config.key_log = Arc::new(rustls::KeyLogFile::new()); - - Ok(TlsAcceptor::from(Arc::new(server_config))) +#[zbus::interface(name = "org.QemuDisplay.RDP")] +impl DBusCtrl { + async fn set_credentials(&self, username: &str, password: &str, domain: &str) { + self.ev + .send(ServerEvent::SetCredentials(Credentials { + username: username.into(), + password: password.into(), + domain: if domain.is_empty() { + None + } else { + Some(domain.into()) + }, + })) + .unwrap(); + } }