Skip to content

Commit c710909

Browse files
authored
feat: inital support for .RDP files (#862)
This is paving the way for .rdp file support. Issue: ARC-339 Issue: ARC-355
1 parent 14e245d commit c710909

File tree

17 files changed

+657
-8
lines changed

17 files changed

+657
-8
lines changed

ARCHITECTURE.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,14 @@ RDCleanPath PDU structure used by IronRDP web client and Devolutions Gateway.
123123
Lightweight and `no_std`-compatible generic `Error` and `Report` types.
124124
The `Error` type wraps a custom consumer-defined type for domain-specific details (such as `PduErrorKind`).
125125

126+
#### [`crates/ironrdp-propertyset`](./crates/ironrdp-propertyset)
127+
128+
The main type is `PropertySet`, a key-value store for configuration options.
129+
130+
#### [`crates/ironrdp-rdpfile`](./crates/ironrdp-rdpfile)
131+
132+
Loader and writer for the .RDP file format.
133+
126134
### Extra Tier
127135

128136
Higher level libraries and binaries built on top of the core tier.
@@ -188,6 +196,10 @@ Web-based frontend using `Svelte` and `Material` frameworks.
188196

189197
Native CLIPRDR backend implementations.
190198

199+
#### [`crates/ironrdp-cfg`](./crates/ironrdp-cfg)
200+
201+
IronRDP-related utilities for ironrdp-propertyset.
202+
191203
### Internal Tier
192204

193205
Crates that are only used inside the IronRDP project, not meant to be published.

Cargo.lock

Lines changed: 26 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/ironrdp-cfg/Cargo.toml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[package]
2+
name = "ironrdp-cfg"
3+
version = "0.1.0"
4+
readme = "README.md"
5+
description = "IronRDP utilities for ironrdp-cfgstore"
6+
publish = false # TODO: publish
7+
edition.workspace = true
8+
license.workspace = true
9+
homepage.workspace = true
10+
repository.workspace = true
11+
authors.workspace = true
12+
keywords.workspace = true
13+
categories.workspace = true
14+
15+
[lib]
16+
doctest = false
17+
test = false
18+
19+
[dependencies]
20+
ironrdp-propertyset = { path = "../ironrdp-propertyset", version = "0.1" } # public
21+
22+
[lints]
23+
workspace = true

crates/ironrdp-cfg/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# IronRDP Configuration
2+
3+
IronRDP-related utilities for ironrdp-propertyset.
4+
5+
This crate is part of the [IronRDP] project.
6+
7+
[IronRDP]: https://github.com/Devolutions/IronRDP

crates/ironrdp-cfg/src/lib.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// QUESTION: consider auto-generating this file based on a reference file?
2+
// https://gist.github.com/awakecoding/838c7fe2ed3a6208e3ca5d8af25363f6
3+
4+
use ironrdp_propertyset::PropertySet;
5+
6+
pub trait PropertySetExt {
7+
fn full_address(&self) -> Option<&str>;
8+
9+
fn server_port(&self) -> Option<i64>;
10+
11+
fn alternate_full_address(&self) -> Option<&str>;
12+
13+
fn gateway_hostname(&self) -> Option<&str>;
14+
15+
fn remote_application_name(&self) -> Option<&str>;
16+
17+
fn remote_application_program(&self) -> Option<&str>;
18+
19+
fn kdc_proxy_url(&self) -> Option<&str>;
20+
21+
fn username(&self) -> Option<&str>;
22+
23+
/// Target RDP server password - use for testing only
24+
fn clear_text_password(&self) -> Option<&str>;
25+
}
26+
27+
impl PropertySetExt for PropertySet {
28+
fn full_address(&self) -> Option<&str> {
29+
self.get::<&str>("full address")
30+
}
31+
32+
fn server_port(&self) -> Option<i64> {
33+
self.get::<i64>("server port")
34+
}
35+
36+
fn alternate_full_address(&self) -> Option<&str> {
37+
self.get::<&str>("alternate full address")
38+
}
39+
40+
fn gateway_hostname(&self) -> Option<&str> {
41+
self.get::<&str>("gatewayhostname")
42+
}
43+
44+
fn remote_application_name(&self) -> Option<&str> {
45+
self.get::<&str>("remoteapplicationname")
46+
}
47+
48+
fn remote_application_program(&self) -> Option<&str> {
49+
self.get::<&str>("remoteapplicationprogram")
50+
}
51+
52+
fn kdc_proxy_url(&self) -> Option<&str> {
53+
self.get::<&str>("kdcproxyurl")
54+
}
55+
56+
fn username(&self) -> Option<&str> {
57+
self.get::<&str>("username")
58+
}
59+
60+
fn clear_text_password(&self) -> Option<&str> {
61+
self.get::<&str>("ClearTextPassword")
62+
}
63+
}

crates/ironrdp-client/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ ironrdp-tls = { path = "../ironrdp-tls", version = "0.1" }
4949
ironrdp-tokio = { path = "../ironrdp-tokio", version = "0.6", features = ["reqwest"] }
5050
ironrdp-rdcleanpath.path = "../ironrdp-rdcleanpath"
5151
ironrdp-dvc-pipe-proxy.path = "../ironrdp-dvc-pipe-proxy"
52+
ironrdp-propertyset.path = "../ironrdp-propertyset"
53+
ironrdp-rdpfile.path = "../ironrdp-rdpfile"
54+
ironrdp-cfg.path = "../ironrdp-cfg"
5255

5356
# Windowing and rendering
5457
winit = { version = "0.30", features = ["rwh_06"] }

crates/ironrdp-client/src/config.rs

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use clap::Parser;
88
use ironrdp::connector::{self, Credentials};
99
use ironrdp::pdu::rdp::capability_sets::{client_codecs_capabilities, MajorPlatformType};
1010
use ironrdp::pdu::rdp::client_info::PerformanceFlags;
11+
use std::path::PathBuf;
1112
use tap::prelude::*;
1213
use url::Url;
1314

@@ -176,13 +177,17 @@ impl FromStr for DvcProxyInfo {
176177
#[clap(author = "Devolutions", about = "Devolutions-IronRDP client")]
177178
#[clap(version, long_about = None)]
178179
struct Args {
180+
/// An address on which the client will connect.
181+
destination: Option<Destination>,
182+
183+
/// Path to a .rdp file to read the configuration from.
184+
#[clap(long)]
185+
rdp_file: Option<PathBuf>,
186+
179187
/// A file with IronRDP client logs
180188
#[clap(short, long)]
181189
log_file: Option<String>,
182190

183-
/// An address on which the client will connect.
184-
destination: Option<Destination>,
185-
186191
/// A target RDP server user name
187192
#[clap(short, long)]
188193
username: Option<String>,
@@ -273,21 +278,46 @@ struct Args {
273278
#[clap(long, num_args = 1.., value_delimiter = ',')]
274279
codecs: Vec<String>,
275280

276-
/// Add DVC channel named pipe proxy.
277-
/// the format is <name>=<pipe>
278-
/// e.g. `ChannelName=PipeName` where `ChannelName` is the name of the channel,
279-
/// and `PipeName` is the name of the named pipe to connect to (without OS-specific prefix),
280-
/// e.g. PipeName will automatically be prefixed with `\\.\pipe\` on Windows.
281+
/// Add DVC channel named pipe proxy
282+
///
283+
/// The format is `<name>=<pipe>`, e.g., `ChannelName=PipeName` where `ChannelName` is the name of the channel,
284+
/// and `PipeName` is the name of the named pipe to connect to (without OS-specific prefix).
285+
/// `<pipe>` will automatically be prefixed with `\\.\pipe\` on Windows.
281286
#[clap(long)]
282287
dvc_proxy: Vec<DvcProxyInfo>,
283288
}
284289

285290
impl Config {
286291
pub fn parse_args() -> anyhow::Result<Self> {
292+
use ironrdp_cfg::PropertySetExt as _;
293+
287294
let args = Args::parse();
288295

296+
let mut properties = ironrdp_propertyset::PropertySet::new();
297+
298+
if let Some(rdp_file) = args.rdp_file {
299+
let input =
300+
std::fs::read_to_string(&rdp_file).with_context(|| format!("failed to read {}", rdp_file.display()))?;
301+
302+
if let Err(errors) = ironrdp_rdpfile::load(&mut properties, &input) {
303+
for e in errors {
304+
#[expect(clippy::print_stderr)]
305+
{
306+
eprintln!("Error when reading {}: {e}", rdp_file.display())
307+
}
308+
}
309+
}
310+
}
311+
289312
let destination = if let Some(destination) = args.destination {
290313
destination
314+
} else if let Some(destination) = properties.full_address() {
315+
if let Some(port) = properties.server_port() {
316+
format!("{destination}:{port}").parse()
317+
} else {
318+
destination.parse()
319+
}
320+
.context("invalid destination")?
291321
} else {
292322
inquire::Text::new("Server address:")
293323
.prompt()
@@ -297,12 +327,16 @@ impl Config {
297327

298328
let username = if let Some(username) = args.username {
299329
username
330+
} else if let Some(username) = properties.username() {
331+
username.to_owned()
300332
} else {
301333
inquire::Text::new("Username:").prompt().context("Username prompt")?
302334
};
303335

304336
let password = if let Some(password) = args.password {
305337
password
338+
} else if let Some(password) = properties.clear_text_password() {
339+
password.to_owned()
306340
} else {
307341
inquire::Password::new("Password:")
308342
.without_confirmation()

crates/ironrdp-error/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#[cfg(feature = "alloc")]
66
extern crate alloc;
7+
78
#[cfg(feature = "alloc")]
89
use alloc::boxed::Box;
910
use core::fmt;

crates/ironrdp-propertyset/Cargo.toml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[package]
2+
name = "ironrdp-propertyset"
3+
version = "0.1.0"
4+
readme = "README.md"
5+
description = "A key-value store for configuration options"
6+
publish = false # TODO: publish
7+
edition.workspace = true
8+
license.workspace = true
9+
homepage.workspace = true
10+
repository.workspace = true
11+
authors.workspace = true
12+
keywords.workspace = true
13+
categories.workspace = true
14+
15+
[lib]
16+
doctest = false
17+
test = false
18+
19+
[dependencies]
20+
tracing = { version = "0.1", features = ["log"] }
21+
22+
[lints]
23+
workspace = true

crates/ironrdp-propertyset/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# IronRDP PropertySet
2+
3+
The main type is `PropertySet`, a key-value store for configuration options.
4+
5+
This crate is part of the [IronRDP] project.
6+
7+
[IronRDP]: https://github.com/Devolutions/IronRDP

0 commit comments

Comments
 (0)