1#![cfg_attr(doc, doc = include_str!("../README.md"))]
2#![doc(html_logo_url = "https://cdnweb.devolutions.net/images/projects/devolutions/logos/devolutions-icon-shadow.svg")]
3
4use core::fmt;
5
6use der::asn1::OctetString;
7
8#[rustfmt::skip] pub use der;
11
12pub const BASE_VERSION: u64 = 3389;
13pub const VERSION_1: u64 = BASE_VERSION + 1;
14
15pub const GENERAL_ERROR_CODE: u16 = 1;
16pub const NEGOTIATION_ERROR_CODE: u16 = 2;
17
18#[derive(Clone, Debug, Eq, PartialEq, der::Sequence)]
19#[asn1(tag_mode = "EXPLICIT")]
20pub struct RDCleanPathErr {
21 #[asn1(context_specific = "0")]
22 pub error_code: u16,
23 #[asn1(context_specific = "1", optional = "true")]
24 pub http_status_code: Option<u16>,
25 #[asn1(context_specific = "2", optional = "true")]
26 pub wsa_last_error: Option<u16>,
27 #[asn1(context_specific = "3", optional = "true")]
28 pub tls_alert_code: Option<u8>,
29}
30
31impl fmt::Display for RDCleanPathErr {
32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 let error_description = match self.error_code {
34 GENERAL_ERROR_CODE => "general error",
35 NEGOTIATION_ERROR_CODE => "negotiation error",
36 _ => "unknown error",
37 };
38 write!(f, "{error_description} (code {})", self.error_code)?;
39
40 if let Some(http_status_code) = self.http_status_code {
41 let description = match http_status_code {
42 200 => "OK",
43 400 => "bad request",
44 401 => "unauthorized",
45 403 => "forbidden",
46 404 => "not found",
47 405 => "method not allowed",
48 408 => "request timeout",
49 409 => "conflict",
50 410 => "gone",
51 413 => "payload too large",
52 414 => "URI too long",
53 422 => "unprocessable entity",
54 429 => "too many requests",
55 500 => "internal server error",
56 501 => "not implemented",
57 502 => "bad gateway",
58 503 => "service unavailable",
59 504 => "gateway timeout",
60 505 => "HTTP version not supported",
61 _ => "unknown HTTP status",
62 };
63 write!(f, "; HTTP {http_status_code} {description}")?;
64 }
65
66 if let Some(wsa_last_error) = self.wsa_last_error {
67 let description = match wsa_last_error {
68 10004 => "interrupted system call",
69 10009 => "bad file descriptor",
70 10013 => "permission denied",
71 10014 => "bad address",
72 10022 => "invalid argument",
73 10024 => "too many open files",
74 10035 => "resource temporarily unavailable",
75 10036 => "operation now in progress",
76 10037 => "operation already in progress",
77 10038 => "socket operation on nonsocket",
78 10039 => "destination address required",
79 10040 => "message too long",
80 10041 => "protocol wrong type for socket",
81 10042 => "bad protocol option",
82 10043 => "protocol not supported",
83 10044 => "socket type not supported",
84 10045 => "operation not supported",
85 10046 => "protocol family not supported",
86 10047 => "address family not supported by protocol family",
87 10048 => "address already in use",
88 10049 => "cannot assign requested address",
89 10050 => "network is down",
90 10051 => "network is unreachable",
91 10052 => "network dropped connection on reset",
92 10053 => "software caused connection abort",
93 10054 => "connection reset by peer",
94 10055 => "no buffer space available",
95 10056 => "socket is already connected",
96 10057 => "socket is not connected",
97 10058 => "cannot send after socket shutdown",
98 10060 => "connection timed out",
99 10061 => "connection refused",
100 10064 => "host is down",
101 10065 => "no route to host",
102 10067 => "too many processes",
103 10091 => "network subsystem is unavailable",
104 10092 => "Winsock version not supported",
105 10093 => "successful WSAStartup not yet performed",
106 10101 => "graceful shutdown in progress",
107 10109 => "class type not found",
108 11001 => "host not found",
109 11002 => "nonauthoritative host not found",
110 11003 => "this is a nonrecoverable error",
111 11004 => "valid name, no data record of requested type",
112 _ => "unknown WSA error",
113 };
114 write!(f, "; WSA {wsa_last_error} {description}")?;
115 }
116
117 if let Some(tls_alert_code) = self.tls_alert_code {
118 let description = match tls_alert_code {
119 0 => "close notify",
120 10 => "unexpected message",
121 20 => "bad record MAC",
122 21 => "decryption failed",
123 22 => "record overflow",
124 30 => "decompression failure",
125 40 => "handshake failure",
126 41 => "no certificate",
127 42 => "bad certificate",
128 43 => "unsupported certificate",
129 44 => "certificate revoked",
130 45 => "certificate expired",
131 46 => "certificate unknown",
132 47 => "illegal parameter",
133 48 => "unknown CA",
134 49 => "access denied",
135 50 => "decode error",
136 51 => "decrypt error",
137 60 => "export restriction",
138 70 => "protocol version",
139 71 => "insufficient security",
140 80 => "internal error",
141 90 => "user canceled",
142 100 => "no renegotiation",
143 109 => "missing extension",
144 110 => "unsupported extension",
145 111 => "certificate unobtainable",
146 112 => "unrecognized name",
147 113 => "bad certificate status response",
148 114 => "bad certificate hash value",
149 115 => "unknown PSK identity",
150 116 => "certificate required",
151 120 => "no application protocol",
152 _ => "unknown TLS alert",
153 };
154 write!(f, "; TLS alert {tls_alert_code} {description}")?;
155 }
156
157 Ok(())
158 }
159}
160
161impl core::error::Error for RDCleanPathErr {}
162
163#[derive(Clone, Debug, Eq, PartialEq, der::Sequence)]
164#[asn1(tag_mode = "EXPLICIT")]
165pub struct RDCleanPathPdu {
166 #[asn1(context_specific = "0")]
168 pub version: u64,
169 #[asn1(context_specific = "1", optional = "true")]
173 pub error: Option<RDCleanPathErr>,
174 #[asn1(context_specific = "2", optional = "true")]
178 pub destination: Option<String>,
179 #[asn1(context_specific = "3", optional = "true")]
183 pub proxy_auth: Option<String>,
184 #[asn1(context_specific = "4", optional = "true")]
186 pub server_auth: Option<String>,
187 #[asn1(context_specific = "5", optional = "true")]
191 pub preconnection_blob: Option<String>,
192 #[asn1(context_specific = "6", optional = "true")]
196 pub x224_connection_pdu: Option<OctetString>,
197 #[asn1(context_specific = "7", optional = "true")]
201 pub server_cert_chain: Option<Vec<OctetString>>,
202 #[asn1(context_specific = "9", optional = "true")]
208 pub server_addr: Option<String>,
209}
210
211impl Default for RDCleanPathPdu {
212 fn default() -> Self {
213 Self {
214 version: VERSION_1,
215 error: None,
216 destination: None,
217 proxy_auth: None,
218 server_auth: None,
219 preconnection_blob: None,
220 x224_connection_pdu: None,
221 server_cert_chain: None,
222 server_addr: None,
223 }
224 }
225}
226
227#[derive(Debug, Clone, PartialEq)]
228pub enum DetectionResult {
229 Detected { version: u64, total_length: usize },
230 NotEnoughBytes,
231 Failed,
232}
233
234impl RDCleanPathPdu {
235 pub fn from_der(src: &[u8]) -> der::Result<Self> {
237 der::Decode::from_der(src)
238 }
239
240 pub fn detect(src: &[u8]) -> DetectionResult {
242 use der::{Decode as _, Encode as _};
243
244 let Ok(mut slice_reader) = der::SliceReader::new(src) else {
245 return DetectionResult::Failed;
246 };
247
248 let header = match der::Header::decode(&mut slice_reader) {
249 Ok(header) => header,
250 Err(e) => match e.kind() {
251 der::ErrorKind::Incomplete { .. } => return DetectionResult::NotEnoughBytes,
252 _ => return DetectionResult::Failed,
253 },
254 };
255
256 let (Ok(header_encoded_len), Ok(body_length)) = (
257 header.encoded_len().and_then(usize::try_from),
258 usize::try_from(header.length),
259 ) else {
260 return DetectionResult::Failed;
261 };
262
263 let Some(total_length) = header_encoded_len.checked_add(body_length) else {
264 return DetectionResult::Failed;
265 };
266
267 match der::asn1::ContextSpecific::<u64>::decode_explicit(&mut slice_reader, der::TagNumber::N0) {
268 Ok(Some(version)) if version.value == VERSION_1 => DetectionResult::Detected {
269 version: VERSION_1,
270 total_length,
271 },
272 Ok(Some(_)) => DetectionResult::Failed,
273 Ok(None) => DetectionResult::NotEnoughBytes,
274 Err(e) => match e.kind() {
275 der::ErrorKind::Incomplete { .. } => DetectionResult::NotEnoughBytes,
276 _ => DetectionResult::Failed,
277 },
278 }
279 }
280
281 pub fn into_enum(self) -> Result<RDCleanPath, MissingRDCleanPathField> {
282 RDCleanPath::try_from(self)
283 }
284
285 pub fn new_general_error() -> Self {
286 Self {
287 version: VERSION_1,
288 error: Some(RDCleanPathErr {
289 error_code: GENERAL_ERROR_CODE,
290 http_status_code: None,
291 wsa_last_error: None,
292 tls_alert_code: None,
293 }),
294 ..Self::default()
295 }
296 }
297
298 pub fn new_http_error(status_code: u16) -> Self {
299 Self {
300 version: VERSION_1,
301 error: Some(RDCleanPathErr {
302 error_code: GENERAL_ERROR_CODE,
303 http_status_code: Some(status_code),
304 wsa_last_error: None,
305 tls_alert_code: None,
306 }),
307 ..Self::default()
308 }
309 }
310
311 pub fn new_request(
312 x224_pdu: Vec<u8>,
313 destination: String,
314 proxy_auth: String,
315 pcb: Option<String>,
316 ) -> der::Result<Self> {
317 Ok(Self {
318 version: VERSION_1,
319 destination: Some(destination),
320 proxy_auth: Some(proxy_auth),
321 preconnection_blob: pcb,
322 x224_connection_pdu: Some(OctetString::new(x224_pdu)?),
323 ..Self::default()
324 })
325 }
326
327 pub fn new_response(
328 server_addr: String,
329 x224_pdu: Vec<u8>,
330 x509_chain: impl IntoIterator<Item = Vec<u8>>,
331 ) -> der::Result<Self> {
332 Ok(Self {
333 version: VERSION_1,
334 x224_connection_pdu: Some(OctetString::new(x224_pdu)?),
335 server_cert_chain: Some(
336 x509_chain
337 .into_iter()
338 .map(OctetString::new)
339 .collect::<der::Result<_>>()?,
340 ),
341 server_addr: Some(server_addr),
342 ..Self::default()
343 })
344 }
345
346 pub fn new_tls_error(alert_code: u8) -> Self {
347 Self {
348 version: VERSION_1,
349 error: Some(RDCleanPathErr {
350 error_code: GENERAL_ERROR_CODE,
351 http_status_code: None,
352 wsa_last_error: None,
353 tls_alert_code: Some(alert_code),
354 }),
355 ..Self::default()
356 }
357 }
358
359 pub fn new_wsa_error(wsa_error_code: u16) -> Self {
360 Self {
361 version: VERSION_1,
362 error: Some(RDCleanPathErr {
363 error_code: GENERAL_ERROR_CODE,
364 http_status_code: None,
365 wsa_last_error: Some(wsa_error_code),
366 tls_alert_code: None,
367 }),
368 ..Self::default()
369 }
370 }
371
372 pub fn new_negotiation_error(server_x224_response: Vec<u8>) -> der::Result<Self> {
387 Ok(Self {
388 version: VERSION_1,
389 error: Some(RDCleanPathErr {
390 error_code: NEGOTIATION_ERROR_CODE,
391 http_status_code: None,
392 wsa_last_error: None,
393 tls_alert_code: None,
394 }),
395 x224_connection_pdu: Some(OctetString::new(server_x224_response)?),
396 ..Self::default()
397 })
398 }
399
400 pub fn to_der(&self) -> der::Result<Vec<u8>> {
401 der::Encode::to_der(self)
402 }
403}
404
405#[derive(Clone, Debug, Eq, PartialEq)]
407pub enum RDCleanPath {
408 Request {
409 destination: String,
410 proxy_auth: String,
411 server_auth: Option<String>,
412 preconnection_blob: Option<String>,
413 x224_connection_request: OctetString,
414 },
415 Response {
416 x224_connection_response: OctetString,
417 server_cert_chain: Vec<OctetString>,
418 server_addr: String,
419 },
420 GeneralErr(RDCleanPathErr),
421 NegotiationErr {
422 x224_connection_response: Vec<u8>,
423 },
424}
425
426impl RDCleanPath {
427 pub fn into_pdu(self) -> RDCleanPathPdu {
428 RDCleanPathPdu::from(self)
429 }
430}
431
432#[derive(Clone, Copy, Debug, Eq, PartialEq)]
433pub struct MissingRDCleanPathField(&'static str);
434
435impl fmt::Display for MissingRDCleanPathField {
436 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
437 write!(f, "RDCleanPath is missing {} field", self.0)
438 }
439}
440
441impl core::error::Error for MissingRDCleanPathField {}
442
443impl TryFrom<RDCleanPathPdu> for RDCleanPath {
444 type Error = MissingRDCleanPathField;
445
446 fn try_from(pdu: RDCleanPathPdu) -> Result<Self, Self::Error> {
447 let rdcleanpath = if let Some(destination) = pdu.destination {
448 Self::Request {
449 destination,
450 proxy_auth: pdu.proxy_auth.ok_or(MissingRDCleanPathField("proxy_auth"))?,
451 server_auth: pdu.server_auth,
452 preconnection_blob: pdu.preconnection_blob,
453 x224_connection_request: pdu
454 .x224_connection_pdu
455 .ok_or(MissingRDCleanPathField("x224_connection_pdu"))?,
456 }
457 } else if let Some(server_addr) = pdu.server_addr {
458 Self::Response {
459 x224_connection_response: pdu
460 .x224_connection_pdu
461 .ok_or(MissingRDCleanPathField("x224_connection_pdu"))?,
462 server_cert_chain: pdu
463 .server_cert_chain
464 .ok_or(MissingRDCleanPathField("server_cert_chain"))?,
465 server_addr,
466 }
467 } else {
468 let error = pdu.error.ok_or(MissingRDCleanPathField("error"))?;
469 match (error.error_code, pdu.x224_connection_pdu) {
470 (NEGOTIATION_ERROR_CODE, Some(x224_pdu)) => Self::NegotiationErr {
471 x224_connection_response: x224_pdu.as_bytes().to_vec(),
472 },
473 _ => Self::GeneralErr(error),
474 }
475 };
476
477 Ok(rdcleanpath)
478 }
479}
480
481impl From<RDCleanPath> for RDCleanPathPdu {
482 fn from(value: RDCleanPath) -> Self {
483 match value {
484 RDCleanPath::Request {
485 destination,
486 proxy_auth,
487 server_auth,
488 preconnection_blob,
489 x224_connection_request,
490 } => Self {
491 version: VERSION_1,
492 destination: Some(destination),
493 proxy_auth: Some(proxy_auth),
494 server_auth,
495 preconnection_blob,
496 x224_connection_pdu: Some(x224_connection_request),
497 ..Default::default()
498 },
499 RDCleanPath::Response {
500 x224_connection_response,
501 server_cert_chain,
502 server_addr,
503 } => Self {
504 version: VERSION_1,
505 x224_connection_pdu: Some(x224_connection_response),
506 server_cert_chain: Some(server_cert_chain),
507 server_addr: Some(server_addr),
508 ..Default::default()
509 },
510 RDCleanPath::GeneralErr(error) => Self {
511 version: VERSION_1,
512 error: Some(error),
513 ..Default::default()
514 },
515 RDCleanPath::NegotiationErr {
516 x224_connection_response,
517 } => Self {
518 version: VERSION_1,
519 error: Some(RDCleanPathErr {
520 error_code: NEGOTIATION_ERROR_CODE,
521 http_status_code: None,
522 wsa_last_error: None,
523 tls_alert_code: None,
524 }),
525 x224_connection_pdu: Some(
526 OctetString::new(x224_connection_response)
527 .expect("x224_connection_response smaller than u32::MAX (256 MiB)"),
528 ),
529 ..Default::default()
530 },
531 }
532 }
533}