Skip to content

Commit 4ab4249

Browse files
committed
feat(agent): add RDM messages handling and proxying over pipe to RDM
1 parent c6eb5a0 commit 4ab4249

File tree

13 files changed

+892
-37
lines changed

13 files changed

+892
-37
lines changed

Cargo.lock

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/devolutions-agent-shared/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ pub struct PackageInfoError {
4343
pub fn get_installed_agent_version() -> Result<Option<DateVersion>, PackageInfoError> {
4444
Ok(windows::registry::get_installed_product_version(
4545
windows::AGENT_UPDATE_CODE,
46+
windows::registry::ProductVersionEncoding::Agent,
4647
)?)
4748
}
4849

crates/devolutions-agent-shared/src/windows/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,13 @@ pub const GATEWAY_UPDATE_CODE: Uuid = uuid!("{db3903d6-c451-4393-bd80-eb9f45b902
1616
///
1717
/// See [`GATEWAY_UPDATE_CODE`] for more information on update codes.
1818
pub const AGENT_UPDATE_CODE: Uuid = uuid!("{82318d3c-811f-4d5d-9a82-b7c31b076755}");
19+
1920
/// MSI upgrade code for the Devolutions Hub Service.
2021
///
2122
/// See [`GATEWAY_UPDATE_CODE`] for more information on update codes.
2223
pub const HUB_SERVICE_UPDATE_CODE: Uuid = uuid!("{f437046e-8e13-430a-8c8f-29fcb9023b59}");
24+
25+
/// MSI upgrade code for the Remote Desktop Manager.
26+
///
27+
/// See [`GATEWAY_UPDATE_CODE`] for more information on update codes.
28+
pub const RDM_UPDATE_CODE: Uuid = uuid!("{2707F3BF-4D7B-40C2-882F-14B0ED869EE8}");

crates/devolutions-agent-shared/src/windows/registry.rs

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,17 @@ pub fn get_product_code(update_code: Uuid) -> Result<Option<Uuid>, RegistryError
4848
Ok(Some(reversed_hex_to_uuid(&product_code)?))
4949
}
5050

51+
pub enum ProductVersionEncoding {
52+
Agent,
53+
Rdm,
54+
}
55+
5156
/// Get the installed version of a product using Windows registry. Returns `None` if the product
5257
/// is not installed.
53-
pub fn get_installed_product_version(update_code: Uuid) -> Result<Option<DateVersion>, RegistryError> {
58+
pub fn get_installed_product_version(
59+
update_code: Uuid,
60+
version_encoding: ProductVersionEncoding,
61+
) -> Result<Option<DateVersion>, RegistryError> {
5462
let product_code_uuid = match get_product_code(update_code)? {
5563
Some(uuid) => uuid,
5664
None => return Ok(None),
@@ -79,7 +87,10 @@ pub fn get_installed_product_version(update_code: Uuid) -> Result<Option<DateVer
7987
})?;
8088

8189
// Convert encoded MSI version number to human-readable date.
82-
let short_year = (product_version >> 24) + 2000;
90+
let short_year = match version_encoding {
91+
ProductVersionEncoding::Agent => (product_version >> 24) + 2000,
92+
ProductVersionEncoding::Rdm => (product_version >> 24) + 0x700,
93+
};
8394
let month = (product_version >> 16) & 0xFF;
8495
let day = product_version & 0xFFFF;
8596

@@ -91,3 +102,36 @@ pub fn get_installed_product_version(update_code: Uuid) -> Result<Option<DateVer
91102
revision: 0,
92103
}))
93104
}
105+
106+
/// Get the installation location of a product using Windows registry. Returns `None` if the product
107+
/// is not installed or if the InstallLocation value is not present.
108+
pub fn get_install_location(update_code: Uuid) -> Result<Option<String>, RegistryError> {
109+
let product_code_uuid = match get_product_code(update_code)? {
110+
Some(uuid) => uuid,
111+
None => return Ok(None),
112+
}
113+
.braced();
114+
115+
let key_path = format!("{REG_CURRENT_VERSION}\\Uninstall\\{product_code_uuid}");
116+
117+
const INSTALL_LOCATION_VALUE_NAME: &str = "InstallLocation";
118+
119+
// Now we know the product code of installed MSI, we could read its install location.
120+
let product_tree = windows_registry::LOCAL_MACHINE
121+
.open(&key_path)
122+
.map_err(|source| RegistryError::OpenKey {
123+
key: key_path.clone(),
124+
source,
125+
})?;
126+
127+
let install_location: String = product_tree
128+
.get_value(INSTALL_LOCATION_VALUE_NAME)
129+
.and_then(TryInto::try_into)
130+
.map_err(|source| RegistryError::ReadValue {
131+
value: INSTALL_LOCATION_VALUE_NAME.to_owned(),
132+
key: key_path.clone(),
133+
source,
134+
})?;
135+
136+
Ok(Some(install_location))
137+
}

crates/win-api-wrappers/src/event.rs

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
use std::sync::Arc;
22

3-
use windows::Win32::Foundation::HANDLE;
4-
use windows::Win32::System::Threading::{CreateEventW, SetEvent};
3+
use anyhow::bail;
4+
use windows::Win32::Foundation::{HANDLE, WAIT_FAILED, WAIT_OBJECT_0, WAIT_TIMEOUT};
5+
use windows::Win32::System::Threading::{CreateEventW, INFINITE, SetEvent, WaitForSingleObject};
56

7+
use crate::Error;
68
use crate::handle::Handle;
9+
use crate::utils::WideString;
710

811
/// RAII wrapper for WinAPI event handle.
912
#[derive(Debug, Clone)]
@@ -24,6 +27,27 @@ impl Event {
2427
})
2528
}
2629

30+
pub fn new_named(name: &str, manual_reset: bool, initial_state: bool) -> anyhow::Result<Self> {
31+
let name_wide = WideString::from(name);
32+
33+
// SAFETY: name_wide is a valid null-terminated UTF-16 string
34+
let raw_handle = unsafe {
35+
CreateEventW(
36+
None, // Default security
37+
manual_reset, // Manual or auto-reset
38+
initial_state, // Initially signaled or not
39+
name_wide.as_pcwstr(), // Event name
40+
)
41+
}?;
42+
43+
// SAFETY: `CreateEventW` always returns a valid handle on success.
44+
let handle = unsafe { Handle::new_owned(raw_handle) }?;
45+
46+
Ok(Self {
47+
handle: Arc::new(handle),
48+
})
49+
}
50+
2751
pub fn raw(&self) -> HANDLE {
2852
self.handle.raw()
2953
}
@@ -35,4 +59,16 @@ impl Event {
3559
}
3660
Ok(())
3761
}
62+
63+
pub fn wait(&self, timeout_ms: Option<u32>) -> anyhow::Result<()> {
64+
// SAFETY: No preconditions.
65+
let status = unsafe { WaitForSingleObject(self.handle.raw(), timeout_ms.unwrap_or(INFINITE)) };
66+
67+
match status {
68+
WAIT_OBJECT_0 => Ok(()),
69+
WAIT_TIMEOUT => bail!("Timeout waiting for event"),
70+
WAIT_FAILED => bail!(Error::last_error()),
71+
_ => bail!("Unexpected wait result"),
72+
}
73+
}
3874
}

crates/win-api-wrappers/src/process.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ use windows::Win32::System::LibraryLoader::{
2424
use windows::Win32::System::RemoteDesktop::ProcessIdToSessionId;
2525
use windows::Win32::System::Threading::{
2626
CREATE_UNICODE_ENVIRONMENT, CreateProcessAsUserW, CreateRemoteThread, EXTENDED_STARTUPINFO_PRESENT,
27-
GetCurrentProcess, GetExitCodeProcess, INFINITE, LPPROC_THREAD_ATTRIBUTE_LIST, LPTHREAD_START_ROUTINE, OpenProcess,
28-
OpenProcessToken, PEB, PROCESS_ACCESS_RIGHTS, PROCESS_BASIC_INFORMATION, PROCESS_CREATION_FLAGS,
29-
PROCESS_INFORMATION, PROCESS_NAME_WIN32, PROCESS_TERMINATE, QueryFullProcessImageNameW, STARTUPINFOEXW,
30-
STARTUPINFOW, STARTUPINFOW_FLAGS, TerminateProcess, WaitForSingleObject,
27+
GetCurrentProcess, GetCurrentProcessId, GetExitCodeProcess, INFINITE, LPPROC_THREAD_ATTRIBUTE_LIST,
28+
LPTHREAD_START_ROUTINE, OpenProcess, OpenProcessToken, PEB, PROCESS_ACCESS_RIGHTS, PROCESS_BASIC_INFORMATION,
29+
PROCESS_CREATION_FLAGS, PROCESS_INFORMATION, PROCESS_NAME_WIN32, PROCESS_TERMINATE, QueryFullProcessImageNameW,
30+
STARTUPINFOEXW, STARTUPINFOW, STARTUPINFOW_FLAGS, TerminateProcess, WaitForSingleObject,
3131
};
3232
use windows::Win32::UI::Shell::{SEE_MASK_NOCLOSEPROCESS, SHELLEXECUTEINFOW, ShellExecuteExW};
3333
use windows::Win32::UI::WindowsAndMessaging::{
@@ -902,13 +902,21 @@ fn terminate_process_by_name_impl(process_name: &str, session_id: Option<u32>) -
902902
Ok(false)
903903
}
904904

905-
fn process_id_to_session(pid: u32) -> Result<u32> {
905+
/// Get the Windows session ID for a given process ID.
906+
pub fn process_id_to_session(pid: u32) -> Result<u32> {
906907
let mut session_id = 0;
907908
// SAFETY: `session_id` is always pointing to a valid memory location.
908909
unsafe { ProcessIdToSessionId(pid, &mut session_id as *mut _) }?;
909910
Ok(session_id)
910911
}
911912

913+
/// Get the current Windows session ID.
914+
pub fn get_current_session_id() -> Result<u32> {
915+
// SAFETY: FFI call with no outstanding preconditions.
916+
let process_id = unsafe { GetCurrentProcessId() };
917+
process_id_to_session(process_id)
918+
}
919+
912920
struct EnumWindowsContext {
913921
expected_pid: u32,
914922
threads: Vec<u32>,

devolutions-agent/src/updater/detect.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ use crate::updater::{Product, UpdaterError};
1010
pub(crate) fn get_installed_product_version(product: Product) -> Result<Option<DateVersion>, UpdaterError> {
1111
match product {
1212
Product::Gateway => {
13-
registry::get_installed_product_version(GATEWAY_UPDATE_CODE).map_err(UpdaterError::WindowsRegistry)
13+
registry::get_installed_product_version(GATEWAY_UPDATE_CODE, registry::ProductVersionEncoding::Agent)
14+
.map_err(UpdaterError::WindowsRegistry)
1415
}
1516
Product::HubService => {
16-
registry::get_installed_product_version(HUB_SERVICE_UPDATE_CODE).map_err(UpdaterError::WindowsRegistry)
17+
registry::get_installed_product_version(HUB_SERVICE_UPDATE_CODE, registry::ProductVersionEncoding::Agent)
18+
.map_err(UpdaterError::WindowsRegistry)
1719
}
1820
}
1921
}

devolutions-session/Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ optional = true
4747
version = "0.4.1"
4848
features = ["std"]
4949

50+
[target.'cfg(windows)'.dependencies]
51+
devolutions-agent-shared = { path = "../crates/devolutions-agent-shared" }
52+
5053
[target.'cfg(windows)'.build-dependencies]
5154
embed-resource = "3.0"
5255

@@ -60,4 +63,4 @@ features = [
6063
"Win32_UI_Shell",
6164
"Win32_System_Console",
6265
"Win32_UI_Input_KeyboardAndMouse",
63-
]
66+
]

devolutions-session/src/dvc/io.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use tracing::{debug, error, info, trace};
55
use windows::Win32::Foundation::{ERROR_IO_PENDING, GetLastError, WAIT_EVENT, WAIT_OBJECT_0};
66
use windows::Win32::Storage::FileSystem::{ReadFile, WriteFile};
77
use windows::Win32::System::IO::{GetOverlappedResult, OVERLAPPED};
8-
use windows::Win32::System::RemoteDesktop::{CHANNEL_CHUNK_LENGTH, CHANNEL_PDU_HEADER};
8+
use windows::Win32::System::RemoteDesktop::CHANNEL_PDU_HEADER;
99
use windows::Win32::System::Threading::{INFINITE, WaitForMultipleObjects};
1010

1111
use now_proto_pdu::NowMessage;
@@ -32,7 +32,9 @@ pub fn run_dvc_io(
3232

3333
trace!("DVC channel opened");
3434

35-
let mut pdu_chunk_buffer = [0u8; CHANNEL_CHUNK_LENGTH as usize];
35+
// All DVC messages should be under CHANNEL_CHUNK_LENGTH size, but sometimes RDP stack
36+
// a few messages together; 128Kb buffer should be enough to hold a few dozen messages.
37+
let mut pdu_chunk_buffer = [0u8; 128 * 1024];
3638
let mut overlapped = OVERLAPPED::default();
3739
let mut bytes_read: u32 = 0;
3840

@@ -112,7 +114,6 @@ pub fn run_dvc_io(
112114
}
113115
}
114116
}
115-
116117
// Prepare async read file operation one more time.
117118
// SAFETY: No preconditions.
118119
let result =

devolutions-session/src/dvc/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ pub mod fs;
3838
pub mod io;
3939
pub mod now_message_dissector;
4040
pub mod process;
41+
pub mod rdm;
4142
pub mod task;
4243

4344
mod env;

0 commit comments

Comments
 (0)