Skip to content

Commit de33c1f

Browse files
committedJan 27, 2025·
command: new service
1 parent ff34608 commit de33c1f

File tree

11 files changed

+190
-8
lines changed

11 files changed

+190
-8
lines changed
 

‎README.md

+7
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ Citrix (work in progress) and native Windows RDP. It supports useful debug servi
1111
soxy has a frontend and a backend component, the latter executes inside a Windows instance managed by one of the supported VDIs, the frontend bridges access to backend functions by exposing VDI-side resources locally using a common protocol. At the time of writing, soxy provides:
1212

1313
- a (basic) FTP server to access the virtual machine's filesystem;
14+
- a telnet-like interface to spawn and interact console/shell executed on the
15+
virtual machine;
1416
- a telnet-like interface to read/write the Windows clipboard of the
1517
virtual machine;
1618
- a SOCKS5 proxy which permits to open connections on client's side as
@@ -258,6 +260,11 @@ command such as nc, and use the available commands:
258260
- `read` or `get`: retrieves the content of the remote clipboard;
259261
- `exit` or `quit`: closes the connection.
260262

263+
#### Remote Console/Shell
264+
265+
Connect to `localhost:3031` on your client machine with a telnet-like
266+
command such as nc, and use the available commands.
267+
261268
#### Remote Filesystem
262269

263270
Connect to `localhost:2021` on your client machine with your favorite

‎common/src/api.rs

+4
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ use std::{borrow, fmt, io, sync};
33
const CHUNK_LENGTH: usize = 1600; // this is the max value
44

55
const SERVICE_CLIPBOARD: &str = "clipboard";
6+
const SERVICE_COMMAND: &str = "command";
67
const SERVICE_FTP: &str = "ftp";
78
const SERVICE_SOCKS5: &str = "socks5";
89

910
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1011
pub enum Service {
1112
Clipboard,
13+
Command,
1214
Ftp,
1315
Socks5,
1416
}
@@ -17,6 +19,7 @@ impl Service {
1719
const fn value(self) -> &'static str {
1820
match self {
1921
Self::Clipboard => SERVICE_CLIPBOARD,
22+
Self::Command => SERVICE_COMMAND,
2023
Self::Ftp => SERVICE_FTP,
2124
Self::Socks5 => SERVICE_SOCKS5,
2225
}
@@ -34,6 +37,7 @@ impl<'a> TryFrom<&'a [u8]> for Service {
3437
let s = String::from_utf8_lossy(value);
3538
match s.as_ref() {
3639
SERVICE_CLIPBOARD => Ok(Self::Clipboard),
40+
SERVICE_COMMAND => Ok(Self::Command),
3741
SERVICE_FTP => Ok(Self::Ftp),
3842
SERVICE_SOCKS5 => Ok(Self::Socks5),
3943
_ => Err(s),

‎common/src/command/backend.rs

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
use crate::{api, service};
2+
use std::{io, process, thread};
3+
4+
const SERVICE: api::Service = api::Service::Command;
5+
const SERVICE_KIND: api::ServiceKind = api::ServiceKind::Backend;
6+
7+
pub struct Server {}
8+
9+
impl service::Backend for Server {
10+
fn accept(rdp_stream: service::RdpStream<'_>) -> Result<(), io::Error> {
11+
let client_id = rdp_stream.client_id();
12+
13+
#[cfg(target_os = "windows")]
14+
let cmd = "cmd.exe";
15+
#[cfg(not(target_os = "windows"))]
16+
let cmd = "sh";
17+
18+
crate::debug!("starting {cmd:?}");
19+
20+
thread::scope(|scope| {
21+
let child = process::Command::new(cmd)
22+
.stdin(process::Stdio::piped())
23+
.stdout(process::Stdio::piped())
24+
.stderr(process::Stdio::piped())
25+
.spawn()?;
26+
27+
let mut stdin = child
28+
.stdin
29+
.ok_or(io::Error::new(io::ErrorKind::InvalidInput, "no stdin"))?;
30+
let mut stdout = child
31+
.stdout
32+
.ok_or(io::Error::new(io::ErrorKind::InvalidInput, "no stdout"))?;
33+
let mut stderr = child
34+
.stderr
35+
.ok_or(io::Error::new(io::ErrorKind::InvalidInput, "no stderr"))?;
36+
37+
let (mut rdp_stream_read, mut rdp_stream_write_out) = rdp_stream.split();
38+
let mut rdp_stream_write_err = rdp_stream_write_out.clone();
39+
40+
thread::Builder::new()
41+
.name(format!("{SERVICE_KIND} {SERVICE} {client_id:x} stdout"))
42+
.spawn_scoped(scope, move || {
43+
if let Err(e) = service::stream_copy(&mut stdout, &mut rdp_stream_write_out) {
44+
crate::debug!("error: {e}");
45+
} else {
46+
crate::debug!("stopped");
47+
}
48+
})
49+
.unwrap();
50+
51+
thread::Builder::new()
52+
.name(format!("{SERVICE_KIND} {SERVICE} {client_id:x} stderr"))
53+
.spawn_scoped(scope, move || {
54+
if let Err(e) = service::stream_copy(&mut stderr, &mut rdp_stream_write_err) {
55+
crate::debug!("error: {e}");
56+
} else {
57+
crate::debug!("stopped");
58+
}
59+
})
60+
.unwrap();
61+
62+
if let Err(e) = service::stream_copy(&mut rdp_stream_read, &mut stdin) {
63+
crate::debug!("error: {e}");
64+
} else {
65+
crate::debug!("stopped");
66+
}
67+
rdp_stream_read.disconnect();
68+
69+
Ok(())
70+
})
71+
}
72+
}

‎common/src/command/frontend.rs

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
use crate::{api, service};
2+
use std::{io, net, thread};
3+
4+
const SERVICE: api::Service = api::Service::Command;
5+
const SERVICE_KIND: api::ServiceKind = api::ServiceKind::Frontend;
6+
7+
pub struct Server {
8+
server: net::TcpListener,
9+
}
10+
11+
impl Server {
12+
fn accept(stream: net::TcpStream) -> Client {
13+
Client { stream }
14+
}
15+
}
16+
17+
impl service::Frontend for Server {
18+
fn bind(tcp: net::SocketAddr) -> Result<Self, io::Error> {
19+
let server = net::TcpListener::bind(tcp)?;
20+
crate::info!("accepting {SERVICE} clients on {}", server.local_addr()?);
21+
Ok(Self { server })
22+
}
23+
24+
fn start(&mut self, channel: &service::Channel) -> Result<(), io::Error> {
25+
thread::scope(|scope| loop {
26+
let (client, client_addr) = self.server.accept()?;
27+
28+
crate::debug!("new client {client_addr}");
29+
30+
let client = Self::accept(client);
31+
32+
thread::Builder::new()
33+
.name(format!("{SERVICE_KIND} {SERVICE} {client_addr}"))
34+
.spawn_scoped(scope, move || {
35+
if let Err(e) = client.start(channel) {
36+
crate::debug!("error: {e}");
37+
}
38+
})?;
39+
})
40+
}
41+
}
42+
43+
struct Client {
44+
stream: net::TcpStream,
45+
}
46+
47+
impl Client {
48+
fn start(self, channel: &service::Channel) -> Result<(), io::Error> {
49+
let client_rdp = channel.connect(SERVICE)?;
50+
service::double_stream_copy(SERVICE_KIND, SERVICE, client_rdp, self.stream)
51+
}
52+
}

‎common/src/command/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pub mod backend;
2+
pub mod frontend;

‎common/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ pub mod log;
33
pub mod service;
44

55
pub mod clipboard;
6+
pub mod command;
67
pub mod ftp;
78
pub mod socks5;
89

‎common/src/service.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::{api, clipboard, ftp, socks5};
1+
use crate::{api, clipboard, command, ftp, socks5};
22
use std::{
33
collections::{self, hash_map},
44
io::{self, Write},
@@ -104,6 +104,11 @@ impl Channel {
104104
crate::debug!("error: {e}");
105105
}
106106
}
107+
api::Service::Command => {
108+
if let Err(e) = command::backend::Server::accept(stream) {
109+
crate::debug!("error: {e}");
110+
}
111+
}
107112
api::Service::Ftp => {
108113
if let Err(e) = ftp::backend::Server::accept(stream) {
109114
crate::error!("error: {e}");
@@ -479,6 +484,7 @@ impl io::Read for RdpReader<'_> {
479484
}
480485
}
481486

487+
#[derive(Clone)]
482488
pub struct RdpWriter<'a> {
483489
control: RdpStreamControl<'a>,
484490
buffer: [u8; api::Chunk::max_payload_length()],
@@ -575,7 +581,7 @@ where
575581
}
576582
}
577583

578-
pub(crate) fn dual_stream_copy(
584+
pub(crate) fn double_stream_copy(
579585
service_kind: api::ServiceKind,
580586
service: api::Service,
581587
rdp_stream: RdpStream<'_>,

‎common/src/socks5/backend.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ impl Server {
3535

3636
crate::debug!("starting stream copy");
3737

38-
service::dual_stream_copy(SERVICE_KIND, SERVICE, stream, server)
38+
service::double_stream_copy(SERVICE_KIND, SERVICE, stream, server)
3939
}
4040
}
4141
}
@@ -66,7 +66,7 @@ impl Server {
6666

6767
crate::debug!("starting stream copy");
6868

69-
service::dual_stream_copy(SERVICE_KIND, SERVICE, stream, client)
69+
service::double_stream_copy(SERVICE_KIND, SERVICE, stream, client)
7070
}
7171
}
7272
}

‎common/src/socks5/frontend.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ impl Client {
137137
return Ok(());
138138
}
139139

140-
service::dual_stream_copy(SERVICE_KIND, SERVICE, client_rdp, self.stream)
140+
service::double_stream_copy(SERVICE_KIND, SERVICE, client_rdp, self.stream)
141141
}
142142

143143
fn command_bind(mut self, mut client_rdp: service::RdpStream<'_>) -> Result<(), io::Error> {
@@ -159,7 +159,7 @@ impl Client {
159159
return Ok(());
160160
}
161161

162-
service::dual_stream_copy(SERVICE_KIND, SERVICE, client_rdp, self.stream)
162+
service::double_stream_copy(SERVICE_KIND, SERVICE, client_rdp, self.stream)
163163
}
164164

165165
fn start(mut self, channel: &service::Channel) -> Result<(), io::Error> {

‎frontend/src/lib.rs

+17-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use common::{
2-
api, clipboard, ftp,
2+
api, clipboard, command, ftp,
33
service::{self, Frontend},
44
socks5,
55
};
@@ -69,6 +69,11 @@ pub(crate) fn init() -> Result<(), Error> {
6969
//let timeout_clipboard = None;
7070
let mut server_clipboard = clipboard::frontend::Server::bind(from_tcp_clipboard)?;
7171

72+
let from_tcp_command =
73+
net::SocketAddr::new(net::IpAddr::V4(net::Ipv4Addr::new(127, 0, 0, 1)), 3031);
74+
//let timeout_command = None;
75+
let mut server_command = command::frontend::Server::bind(from_tcp_command)?;
76+
7277
let from_tcp_ftp =
7378
net::SocketAddr::new(net::IpAddr::V4(net::Ipv4Addr::new(127, 0, 0, 1)), 2021);
7479
//let timeout_ftp = None;
@@ -116,6 +121,17 @@ pub(crate) fn init() -> Result<(), Error> {
116121
})
117122
.unwrap();
118123

124+
thread::Builder::new()
125+
.name(format!("{}", api::Service::Command))
126+
.spawn_scoped(scope, || {
127+
if let Err(e) = server_command.start(&services) {
128+
common::error!("{} error: {e}", api::Service::Command);
129+
} else {
130+
common::debug!("{} terminated", api::Service::Command);
131+
}
132+
})
133+
.unwrap();
134+
119135
thread::Builder::new()
120136
.name(format!("{}", api::Service::Ftp))
121137
.spawn_scoped(scope, || {

‎standalone/src/bin/standalone.rs

+23-1
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
use common::{
2-
api, clipboard, ftp,
2+
api, clipboard, command, ftp,
33
service::{self, Frontend},
44
socks5,
55
};
66
use std::{net, thread};
77

88
const CHANNEL_SIZE: usize = 256;
99

10+
#[allow(clippy::too_many_lines)]
1011
fn main() {
1112
common::init_logs().expect("failed to initialize log");
1213

1314
let from_tcp_clipboard =
1415
net::SocketAddr::new(net::IpAddr::V4(net::Ipv4Addr::new(127, 0, 0, 1)), 3032);
16+
let from_tcp_command =
17+
net::SocketAddr::new(net::IpAddr::V4(net::Ipv4Addr::new(127, 0, 0, 1)), 3031);
1518
let from_tcp_ftp =
1619
net::SocketAddr::new(net::IpAddr::V4(net::Ipv4Addr::new(127, 0, 0, 1)), 2021);
1720
let from_tcp_socks5 =
@@ -26,6 +29,14 @@ fn main() {
2629
Ok(frontend_server) => frontend_server,
2730
};
2831

32+
let mut frontend_server_command = match command::frontend::Server::bind(from_tcp_command) {
33+
Err(e) => {
34+
common::error!("failed to bind to {from_tcp_command}: {e}");
35+
return;
36+
}
37+
Ok(frontend_server) => frontend_server,
38+
};
39+
2940
let mut frontend_server_ftp = match ftp::frontend::Server::bind(from_tcp_ftp) {
3041
Err(e) => {
3142
common::error!("failed to bind to {from_tcp_ftp}: {e}");
@@ -88,6 +99,17 @@ fn main() {
8899
})
89100
.unwrap();
90101

102+
thread::Builder::new()
103+
.name(format!("frontend {}", api::Service::Command))
104+
.spawn_scoped(scope, || {
105+
if let Err(e) = frontend_server_command.start(&frontend_channel) {
106+
common::error!("error: {e}");
107+
} else {
108+
common::debug!("stopped");
109+
}
110+
})
111+
.unwrap();
112+
91113
thread::Builder::new()
92114
.name(format!("frontend {}", api::Service::Ftp))
93115
.spawn_scoped(scope, || {

0 commit comments

Comments
 (0)
Please sign in to comment.