--- /dev/null
+/* Copyright (C) 2019 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+// Author: Zach Kelly <zach.kelly@lmco.com>
+
+use super::rdp::{RdpTransaction, RdpTransactionItem};
+use json::{Json, JsonT};
+use rdp::parser::*;
+use rdp::windows;
+use std;
+use x509_parser::parse_x509_der;
+
+#[no_mangle]
+pub extern "C" fn rs_rdp_to_json(tx: *mut std::os::raw::c_void) -> *mut JsonT {
+ let tx = cast_pointer!(tx, RdpTransaction);
+ match to_json(tx) {
+ Some(js) => js.unwrap(),
+ None => std::ptr::null_mut(),
+ }
+}
+
+/// populate a json object with transactional information, for logging
+fn to_json(tx: &RdpTransaction) -> Option<Json> {
+ let js = Json::object();
+
+ js.set_integer("tx_id", tx.id);
+
+ match &tx.item {
+ RdpTransactionItem::X224ConnectionRequest(ref x224) => {
+ x224_req_to_json(&js, x224)
+ }
+ RdpTransactionItem::X224ConnectionConfirm(x224) => {
+ x224_conf_to_json(&js, x224)
+ }
+
+ RdpTransactionItem::McsConnectRequest(ref mcs) => {
+ mcs_req_to_json(&js, mcs);
+ }
+
+ RdpTransactionItem::McsConnectResponse(_) => {
+ // no additional JSON data beyond `event_type`
+ js.set_string("event_type", "connect_response");
+ }
+
+ RdpTransactionItem::TlsCertificateChain(chain) => {
+ js.set_string("event_type", "tls_handshake");
+ let js_chain = Json::array();
+ for blob in chain {
+ match parse_x509_der(&blob.data) {
+ Ok((_, cert)) => {
+ js_chain.array_append_string(
+ &cert.tbs_certificate.serial.to_str_radix(16),
+ );
+ }
+ _ => {}
+ }
+ }
+ js.set("x509_serials", js_chain);
+ }
+ }
+
+ return Some(js);
+}
+
+/// json helper for X224ConnectionRequest
+fn x224_req_to_json(js: &Json, x224: &X224ConnectionRequest) {
+ use rdp::parser::NegotiationRequestFlags as Flags;
+
+ js.set_string("event_type", "initial_request");
+ if let Some(ref cookie) = x224.cookie {
+ js.set_string("cookie", &cookie.mstshash);
+ }
+ if let Some(ref req) = x224.negotiation_request {
+ if !req.flags.is_empty() {
+ let flags = Json::array();
+ if req.flags.contains(Flags::RESTRICTED_ADMIN_MODE_REQUIRED) {
+ flags.array_append_string("restricted_admin_mode_required");
+ }
+ if req
+ .flags
+ .contains(Flags::REDIRECTED_AUTHENTICATION_MODE_REQUIRED)
+ {
+ flags.array_append_string(
+ "redirected_authentication_mode_required",
+ );
+ }
+ if req.flags.contains(Flags::CORRELATION_INFO_PRESENT) {
+ flags.array_append_string("correlation_info_present");
+ }
+ js.set("flags", flags);
+ }
+ }
+}
+
+/// json helper for X224ConnectionConfirm
+fn x224_conf_to_json(js: &Json, x224: &X224ConnectionConfirm) {
+ use rdp::parser::NegotiationResponseFlags as Flags;
+
+ js.set_string("event_type", "initial_response");
+ if let Some(ref from_server) = x224.negotiation_from_server {
+ match &from_server {
+ NegotiationFromServer::Response(ref resp) => {
+ if !resp.flags.is_empty() {
+ let flags = Json::array();
+ if resp
+ .flags
+ .contains(Flags::EXTENDED_CLIENT_DATA_SUPPORTED)
+ {
+ flags.array_append_string("extended_client_data");
+ }
+ if resp.flags.contains(Flags::DYNVC_GFX_PROTOCOL_SUPPORTED)
+ {
+ flags.array_append_string("dynvc_gfx");
+ }
+
+ // NEGRSP_FLAG_RESERVED not logged
+
+ if resp
+ .flags
+ .contains(Flags::RESTRICTED_ADMIN_MODE_SUPPORTED)
+ {
+ flags.array_append_string("restricted_admin");
+ }
+ if resp.flags.contains(
+ Flags::REDIRECTED_AUTHENTICATION_MODE_SUPPORTED,
+ ) {
+ flags.array_append_string("redirected_authentication");
+ }
+ js.set("server_supports", flags);
+ }
+
+ let protocol = match resp.protocol {
+ Protocol::ProtocolRdp => "rdp",
+ Protocol::ProtocolSsl => "ssl",
+ Protocol::ProtocolHybrid => "hybrid",
+ Protocol::ProtocolRdsTls => "rds_tls",
+ Protocol::ProtocolHybridEx => "hybrid_ex",
+ };
+ js.set_string("protocol", protocol);
+ }
+
+ NegotiationFromServer::Failure(ref fail) => match fail.code {
+ NegotiationFailureCode::SslRequiredByServer => {
+ js.set_integer(
+ "error_code",
+ NegotiationFailureCode::SslRequiredByServer as u64,
+ );
+ js.set_string("reason", "ssl required by server")
+ }
+ NegotiationFailureCode::SslNotAllowedByServer => {
+ js.set_integer(
+ "error_code",
+ NegotiationFailureCode::SslNotAllowedByServer as u64,
+ );
+ js.set_string("reason", "ssl not allowed by server")
+ }
+ NegotiationFailureCode::SslCertNotOnServer => {
+ js.set_integer(
+ "error_code",
+ NegotiationFailureCode::SslCertNotOnServer as u64,
+ );
+ js.set_string("reason", "ssl cert not on server")
+ }
+ NegotiationFailureCode::InconsistentFlags => {
+ js.set_integer(
+ "error_code",
+ NegotiationFailureCode::InconsistentFlags as u64,
+ );
+ js.set_string("reason", "inconsistent flags")
+ }
+ NegotiationFailureCode::HybridRequiredByServer => {
+ js.set_integer(
+ "error_code",
+ NegotiationFailureCode::HybridRequiredByServer as u64,
+ );
+ js.set_string("reason", "hybrid required by server")
+ }
+ NegotiationFailureCode::SslWithUserAuthRequiredByServer => {
+ js.set_integer(
+ "error_code",
+ NegotiationFailureCode::SslWithUserAuthRequiredByServer
+ as u64,
+ );
+ js.set_string(
+ "reason",
+ "ssl with user auth required by server",
+ )
+ }
+ },
+ }
+ }
+}
+
+/// json helper for McsConnectRequest
+fn mcs_req_to_json(js: &Json, mcs: &McsConnectRequest) {
+ // placeholder string value. We do not simply omit "unknown" values so that they can
+ // help indicate that a given enum may be out of date (new Windows version, etc.)
+ let unknown = String::from("unknown");
+
+ js.set_string("event_type", "connect_request");
+ for child in &mcs.children {
+ match child {
+ McsConnectRequestChild::CsClientCore(ref client) => {
+ let js_client = Json::object();
+
+ match client.version {
+ Some(ref ver) => js_client
+ .set_string("version", &version_to_string(ver, "v")),
+ None => js_client.set_string("version", &unknown),
+ }
+
+ js_client
+ .set_integer("desktop_width", client.desktop_width as u64);
+ js_client.set_integer(
+ "desktop_height",
+ client.desktop_height as u64,
+ );
+
+ if let Some(depth) = get_color_depth(client) {
+ js_client.set_integer("color_depth", depth);
+ }
+
+ // sas_sequence not logged
+
+ js_client.set_string(
+ "keyboard_layout",
+ &windows::lcid_to_string(client.keyboard_layout, &unknown),
+ );
+
+ js_client.set_string(
+ "build",
+ &windows::os_to_string(&client.client_build, &unknown),
+ );
+
+ if client.client_name.len() > 0 {
+ js_client.set_string("client_name", &client.client_name);
+ }
+
+ if let Some(ref kb) = client.keyboard_type {
+ js_client
+ .set_string("keyboard_type", &keyboard_to_string(kb));
+ }
+
+ if client.keyboard_subtype != 0 {
+ js_client.set_integer(
+ "keyboard_subtype",
+ client.keyboard_subtype as u64,
+ );
+ }
+
+ if client.keyboard_function_key != 0 {
+ js_client.set_integer(
+ "function_keys",
+ client.keyboard_function_key as u64,
+ );
+ }
+
+ if client.ime_file_name.len() > 0 {
+ js_client.set_string("ime", &client.ime_file_name);
+ }
+
+ //
+ // optional fields
+ //
+
+ if let Some(id) = client.client_product_id {
+ js_client.set_integer("product_id", id as u64);
+ }
+
+ if let Some(serial) = client.serial_number {
+ if serial != 0 {
+ js_client.set_integer("serial_number", serial as u64);
+ }
+ }
+
+ // supported_color_depth not logged
+
+ if let Some(ref early_capability_flags) =
+ client.early_capability_flags
+ {
+ use rdp::parser::EarlyCapabilityFlags as Flags;
+
+ if !early_capability_flags.is_empty() {
+ let flags = Json::array();
+ if early_capability_flags
+ .contains(Flags::RNS_UD_CS_SUPPORT_ERRINFO_PDF)
+ {
+ flags.array_append_string("support_errinfo_pdf");
+ }
+ if early_capability_flags
+ .contains(Flags::RNS_UD_CS_WANT_32BPP_SESSION)
+ {
+ flags.array_append_string("want_32bpp_session");
+ }
+ if early_capability_flags
+ .contains(Flags::RNS_UD_CS_SUPPORT_STATUSINFO_PDU)
+ {
+ flags.array_append_string("support_statusinfo_pdu");
+ }
+ if early_capability_flags
+ .contains(Flags::RNS_UD_CS_STRONG_ASYMMETRIC_KEYS)
+ {
+ flags.array_append_string("strong_asymmetric_keys");
+ }
+
+ // RNS_UD_CS_UNUSED not logged
+
+ if early_capability_flags
+ .contains(Flags::RNS_UD_CS_VALID_CONNECTION_TYPE)
+ {
+ flags.array_append_string("valid_connection_type");
+ }
+ if early_capability_flags.contains(
+ Flags::RNS_UD_CS_SUPPORT_MONITOR_LAYOUT_PDU,
+ ) {
+ flags.array_append_string(
+ "support_monitor_layout_pdu",
+ );
+ }
+ if early_capability_flags.contains(
+ Flags::RNS_UD_CS_SUPPORT_NETCHAR_AUTODETECT,
+ ) {
+ flags.array_append_string(
+ "support_netchar_autodetect",
+ );
+ }
+ if early_capability_flags.contains(
+ Flags::RNS_UD_CS_SUPPORT_DYNVC_GFX_PROTOCOL,
+ ) {
+ flags.array_append_string(
+ "support_dynvc_gfx_protocol",
+ );
+ }
+ if early_capability_flags.contains(
+ Flags::RNS_UD_CS_SUPPORT_DYNAMIC_TIME_ZONE,
+ ) {
+ flags.array_append_string(
+ "support_dynamic_time_zone",
+ );
+ }
+ if early_capability_flags
+ .contains(Flags::RNS_UD_CS_SUPPORT_HEARTBEAT_PDU)
+ {
+ flags.array_append_string("support_heartbeat_pdu");
+ }
+ js_client.set("capabilities", flags);
+ }
+ }
+
+ if let Some(ref id) = client.client_dig_product_id {
+ if id.len() > 0 {
+ js_client.set_string("id", id);
+ }
+ }
+
+ if let Some(ref hint) = client.connection_hint {
+ let s = match hint {
+ ConnectionHint::ConnectionHintModem => "modem",
+ ConnectionHint::ConnectionHintBroadbandLow => {
+ "low_broadband"
+ }
+ ConnectionHint::ConnectionHintSatellite => "satellite",
+ ConnectionHint::ConnectionHintBroadbandHigh => {
+ "high_broadband"
+ }
+ ConnectionHint::ConnectionHintWan => "wan",
+ ConnectionHint::ConnectionHintLan => "lan",
+ ConnectionHint::ConnectionHintAutoDetect => {
+ "autodetect"
+ }
+ ConnectionHint::ConnectionHintNotProvided => "",
+ };
+ if *hint != ConnectionHint::ConnectionHintNotProvided {
+ js_client.set_string("connection_hint", s);
+ }
+ }
+
+ // server_selected_procotol not logged
+
+ if let Some(width) = client.desktop_physical_width {
+ js_client.set_integer("physical_width", width as u64);
+ }
+
+ if let Some(height) = client.desktop_physical_height {
+ js_client.set_integer("physical_height", height as u64);
+ }
+
+ if let Some(orientation) = client.desktop_orientation {
+ js_client
+ .set_integer("desktop_orientation", orientation as u64);
+ }
+
+ if let Some(scale) = client.desktop_scale_factor {
+ js_client.set_integer("scale_factor", scale as u64);
+ }
+
+ if let Some(scale) = client.device_scale_factor {
+ js_client.set_integer("device_scale_factor", scale as u64);
+ }
+ js.set("client", js_client);
+ }
+
+ McsConnectRequestChild::CsNet(ref net) => {
+ if net.channels.len() > 0 {
+ let channels = Json::array();
+ for channel in &net.channels {
+ channels.array_append_string(&channel);
+ }
+ js.set("channels", channels);
+ }
+ }
+
+ McsConnectRequestChild::CsUnknown(_) => {}
+ }
+ }
+}
+
+/// converts RdpClientVersion to a string, using the provided prefix
+fn version_to_string<'a>(ver: &RdpClientVersion, prefix: &'a str) -> String {
+ let mut result = String::from(prefix);
+ match ver {
+ RdpClientVersion::V4 => result.push_str("4"),
+ RdpClientVersion::V5_V8_1 => result.push_str("5"),
+ RdpClientVersion::V10_0 => result.push_str("10.0"),
+ RdpClientVersion::V10_1 => result.push_str("10.1"),
+ RdpClientVersion::V10_2 => result.push_str("10.2"),
+ RdpClientVersion::V10_3 => result.push_str("10.3"),
+ RdpClientVersion::V10_4 => result.push_str("10.4"),
+ RdpClientVersion::V10_5 => result.push_str("10.5"),
+ RdpClientVersion::V10_6 => result.push_str("10.6"),
+ RdpClientVersion::V10_7 => result.push_str("10.7"),
+ };
+ result
+}
+
+/// checks multiple client info fields to determine color depth
+fn get_color_depth(client: &CsClientCoreData) -> Option<u64> {
+ // first check high_color_depth
+ match client.high_color_depth {
+ Some(HighColorDepth::HighColor4Bpp) => return Some(4),
+ Some(HighColorDepth::HighColor8Bpp) => return Some(8),
+ Some(HighColorDepth::HighColor15Bpp) => return Some(15),
+ Some(HighColorDepth::HighColor16Bpp) => return Some(16),
+ Some(HighColorDepth::HighColor24Bpp) => return Some(24),
+ _ => (),
+ };
+
+ // if not present, try post_beta2_color_depth
+ match client.post_beta2_color_depth {
+ Some(PostBeta2ColorDepth::RnsUdColor4Bpp) => return Some(4),
+ Some(PostBeta2ColorDepth::RnsUdColor8Bpp) => return Some(8),
+ Some(PostBeta2ColorDepth::RnsUdColor16Bpp555) => return Some(15),
+ Some(PostBeta2ColorDepth::RnsUdColor16Bpp565) => return Some(16),
+ Some(PostBeta2ColorDepth::RnsUdColor24Bpp) => return Some(24),
+ _ => (),
+ };
+
+ // if not present, try color_depth
+ match client.color_depth {
+ Some(ColorDepth::RnsUdColor4Bpp) => return Some(4),
+ Some(ColorDepth::RnsUdColor8Bpp) => return Some(8),
+ _ => return None,
+ }
+}
+
+fn keyboard_to_string(kb: &KeyboardType) -> String {
+ let s = match kb {
+ KeyboardType::KbXt => "xt",
+ KeyboardType::KbIco => "ico",
+ KeyboardType::KbAt => "at",
+ KeyboardType::KbEnhanced => "enhanced",
+ KeyboardType::Kb1050 => "1050",
+ KeyboardType::Kb9140 => "9140",
+ KeyboardType::KbJapanese => "jp",
+ };
+ String::from(s)
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ // for now, unsure how to effectively test Json/JsonT
+
+ #[test]
+ fn test_version_string() {
+ assert_eq!("v10.7", version_to_string(&RdpClientVersion::V10_7, "v"));
+ }
+
+ #[test]
+ fn test_color_depth_high() {
+ let core_data = CsClientCoreData {
+ version: None,
+ desktop_width: 1280,
+ desktop_height: 768,
+ color_depth: Some(ColorDepth::RnsUdColor4Bpp),
+ sas_sequence: None,
+ keyboard_layout: 0x409,
+ client_build: windows::OperatingSystem {
+ build: windows::Build::Win10_17763,
+ suffix: windows::Suffix::Rs5,
+ },
+ client_name: String::from("SERVER-XYZ"),
+ keyboard_type: None,
+ keyboard_subtype: 0,
+ keyboard_function_key: 12,
+ ime_file_name: String::from(""),
+ post_beta2_color_depth: Some(PostBeta2ColorDepth::RnsUdColor8Bpp),
+ client_product_id: None,
+ serial_number: None,
+ high_color_depth: Some(HighColorDepth::HighColor24Bpp),
+ supported_color_depth: None,
+ early_capability_flags: None,
+ client_dig_product_id: None,
+ connection_hint: None,
+ server_selected_protocol: None,
+ desktop_physical_width: None,
+ desktop_physical_height: None,
+ desktop_orientation: None,
+ desktop_scale_factor: None,
+ device_scale_factor: None,
+ };
+ assert_eq!(Some(24), get_color_depth(&core_data));
+ }
+
+ #[test]
+ fn test_color_depth_post_beta2() {
+ let core_data = CsClientCoreData {
+ version: None,
+ desktop_width: 1280,
+ desktop_height: 768,
+ color_depth: Some(ColorDepth::RnsUdColor4Bpp),
+ sas_sequence: None,
+ keyboard_layout: 0x409,
+ client_build: windows::OperatingSystem {
+ build: windows::Build::Win10_17763,
+ suffix: windows::Suffix::Rs5,
+ },
+ client_name: String::from("SERVER-XYZ"),
+ keyboard_type: None,
+ keyboard_subtype: 0,
+ keyboard_function_key: 12,
+ ime_file_name: String::from(""),
+ post_beta2_color_depth: Some(PostBeta2ColorDepth::RnsUdColor8Bpp),
+ client_product_id: None,
+ serial_number: None,
+ high_color_depth: None,
+ supported_color_depth: None,
+ early_capability_flags: None,
+ client_dig_product_id: None,
+ connection_hint: None,
+ server_selected_protocol: None,
+ desktop_physical_width: None,
+ desktop_physical_height: None,
+ desktop_orientation: None,
+ desktop_scale_factor: None,
+ device_scale_factor: None,
+ };
+ assert_eq!(Some(8), get_color_depth(&core_data));
+ }
+
+ #[test]
+ fn test_color_depth_basic() {
+ let core_data = CsClientCoreData {
+ version: None,
+ desktop_width: 1280,
+ desktop_height: 768,
+ color_depth: Some(ColorDepth::RnsUdColor4Bpp),
+ sas_sequence: None,
+ keyboard_layout: 0x409,
+ client_build: windows::OperatingSystem {
+ build: windows::Build::Win10_17763,
+ suffix: windows::Suffix::Rs5,
+ },
+ client_name: String::from("SERVER-XYZ"),
+ keyboard_type: None,
+ keyboard_subtype: 0,
+ keyboard_function_key: 12,
+ ime_file_name: String::from(""),
+ post_beta2_color_depth: None,
+ client_product_id: None,
+ serial_number: None,
+ high_color_depth: None,
+ supported_color_depth: None,
+ early_capability_flags: None,
+ client_dig_product_id: None,
+ connection_hint: None,
+ server_selected_protocol: None,
+ desktop_physical_width: None,
+ desktop_physical_height: None,
+ desktop_orientation: None,
+ desktop_scale_factor: None,
+ device_scale_factor: None,
+ };
+ assert_eq!(Some(4), get_color_depth(&core_data));
+ }
+
+ #[test]
+ fn test_color_depth_missing() {
+ let core_data = CsClientCoreData {
+ version: None,
+ desktop_width: 1280,
+ desktop_height: 768,
+ color_depth: None,
+ sas_sequence: None,
+ keyboard_layout: 0x409,
+ client_build: windows::OperatingSystem {
+ build: windows::Build::Win10_17763,
+ suffix: windows::Suffix::Rs5,
+ },
+ client_name: String::from("SERVER-XYZ"),
+ keyboard_type: None,
+ keyboard_subtype: 0,
+ keyboard_function_key: 12,
+ ime_file_name: String::from(""),
+ post_beta2_color_depth: None,
+ client_product_id: None,
+ serial_number: None,
+ high_color_depth: None,
+ supported_color_depth: None,
+ early_capability_flags: None,
+ client_dig_product_id: None,
+ connection_hint: None,
+ server_selected_protocol: None,
+ desktop_physical_width: None,
+ desktop_physical_height: None,
+ desktop_orientation: None,
+ desktop_scale_factor: None,
+ device_scale_factor: None,
+ };
+ assert!(get_color_depth(&core_data).is_none());
+ }
+
+ #[test]
+ fn test_keyboard_string() {
+ assert_eq!("enhanced", keyboard_to_string(&KeyboardType::KbEnhanced));
+ }
+}
--- /dev/null
+/* Copyright (C) 2019 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+// Author: Zach Kelly <zach.kelly@lmco.com>
+
+//! RDP parser
+//!
+//! References:
+//! * rdp-spec: <https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-rdpbcgr/>
+//! * t.123-spec: <https://www.itu.int/rec/T-REC-T.123-200701-I/en>
+//! * t.125-spec: <https://www.itu.int/rec/T-REC-T.125-199802-I/en>
+//! * x.224-spec: <https://www.itu.int/rec/T-REC-X.224-199511-I/en>
+//! * x.691-spec: <https://www.itu.int/rec/T-REC-X.691/en>
+
+use nom::{be_u16, be_u8, le_u16, le_u32, le_u8, ErrorKind, IResult};
+use rdp::error::RDP_NOT_X224_CLASS_0_ERROR;
+use rdp::util::{
+ le_slice_to_string, parse_per_length_determinant, utf7_slice_to_string,
+};
+use rdp::windows;
+
+/// constrains dimension to a range, per spec
+/// rdp-spec, section 2.2.1.3.2 Client Core Data
+fn millimeters_to_opt(x: u32) -> Option<u32> {
+ if x >= 10 && x <= 10_000 {
+ Some(x)
+ } else {
+ None
+ }
+}
+
+/// constrains desktop scale to a range, per spec
+/// rdp-spec, section 2.2.1.3.2 Client Core Data
+fn desktop_scale_to_opt(x: u32) -> Option<u32> {
+ if x >= 100 && x <= 500 {
+ Some(x)
+ } else {
+ None
+ }
+}
+
+/// constrains device scale to a range, per spec
+/// rdp-spec, section 2.2.1.3.2 Client Core Data
+fn device_scale_to_opt(x: u32) -> Option<u32> {
+ if x == 100 || x == 140 || x == 180 {
+ Some(x)
+ } else {
+ None
+ }
+}
+
+// ================
+
+/// t.123-spec, section 8
+#[derive(Clone, Debug, PartialEq)]
+pub enum TpktVersion {
+ T123 = 0x3,
+}
+
+/// t.123-spec, section 8
+#[derive(Clone, Debug, PartialEq)]
+pub struct T123Tpkt {
+ pub child: T123TpktChild,
+}
+
+/// variants that a t.123 tpkt can hold
+#[derive(Clone, Debug, PartialEq)]
+pub enum T123TpktChild {
+ X224ConnectionRequest(X224ConnectionRequest),
+ X224ConnectionConfirm(X224ConnectionConfirm),
+ Data(X223Data),
+ Raw(Vec<u8>),
+}
+
+// ================
+
+/// x.224-spec, sections 13.3.3, 13.4.3, 13.7.3
+#[derive(Clone, Debug, PartialEq)]
+pub enum X224Type {
+ ConnectionConfirm = 0xd,
+ ConnectionRequest = 0xe,
+ Data = 0xf,
+}
+
+/// x.224-spec, section 13.3
+// rdp-spec, section 2.2.1.1
+#[derive(Clone, Debug, PartialEq)]
+pub struct X224ConnectionRequest {
+ pub cdt: u8,
+ pub dst_ref: u16,
+ pub src_ref: u16,
+ pub class: u8,
+ pub options: u8,
+ pub cookie: Option<RdpCookie>,
+ pub negotiation_request: Option<NegotiationRequest>,
+ pub data: Vec<u8>,
+}
+
+/// rdp-spec, section 2.2.1.1.1
+#[derive(Clone, Debug, PartialEq)]
+pub struct RdpCookie {
+ pub mstshash: String,
+}
+
+/// rdp-spec, sections 2.2.1.1.1, 2.2.1.2.1, 2.2.1.2.2
+#[derive(Clone, Debug, PartialEq)]
+pub enum X224ConnectionRequestType {
+ NegotiationRequest = 0x1,
+ NegotiationResponse = 0x2,
+ NegotiationFailure = 0x3,
+}
+
+/// rdp-spec, section 2.2.1.1.1
+#[derive(Clone, Debug, PartialEq)]
+pub struct NegotiationRequest {
+ pub flags: NegotiationRequestFlags,
+ pub protocols: ProtocolFlags,
+}
+
+// rdp-spec, section 2.2.1.1.1
+bitflags! {
+ #[derive(Default)]
+ pub struct NegotiationRequestFlags: u8 {
+ const RESTRICTED_ADMIN_MODE_REQUIRED = 0x1;
+ const REDIRECTED_AUTHENTICATION_MODE_REQUIRED = 0x2;
+ const CORRELATION_INFO_PRESENT = 0x8;
+ }
+}
+
+/// rdp-spec, section 2.2.1.1.1
+#[derive(Clone, Debug, FromPrimitive, PartialEq)]
+pub enum Protocol {
+ ProtocolRdp = 0x0,
+ ProtocolSsl = 0x1,
+ ProtocolHybrid = 0x2,
+ ProtocolRdsTls = 0x4,
+ ProtocolHybridEx = 0x8,
+}
+
+// rdp-spec, section 2.2.1.1.1
+bitflags! {
+ pub struct ProtocolFlags: u32 {
+ const PROTOCOL_RDP = Protocol::ProtocolRdp as u32;
+ const PROTOCOL_SSL = Protocol::ProtocolSsl as u32;
+ const PROTOCOL_HYBRID = Protocol::ProtocolHybrid as u32;
+ const PROTOCOL_RDSTLS = Protocol::ProtocolRdsTls as u32;
+ const PROTOCOL_HYBRID_EX = Protocol::ProtocolHybridEx as u32;
+ }
+}
+
+/// rdp-spec, section 2.2.1.2
+/// x.224-spec, section 13.3
+#[derive(Clone, Debug, PartialEq)]
+pub struct X224ConnectionConfirm {
+ pub cdt: u8,
+ pub dst_ref: u16,
+ pub src_ref: u16,
+ pub class: u8,
+ pub options: u8,
+ pub negotiation_from_server: Option<NegotiationFromServer>,
+}
+
+/// variants of a server negotiation
+#[derive(Clone, Debug, PartialEq)]
+pub enum NegotiationFromServer {
+ Response(NegotiationResponse),
+ Failure(NegotiationFailure),
+}
+
+/// rdp-spec, section 2.2.1.1.1
+#[derive(Clone, Debug, PartialEq)]
+pub struct NegotiationResponse {
+ pub flags: NegotiationResponseFlags,
+ pub protocol: Protocol,
+}
+
+// rdp-spec, section 2.2.1.2.1
+bitflags! {
+ #[derive(Default)]
+ pub struct NegotiationResponseFlags: u8 {
+ const EXTENDED_CLIENT_DATA_SUPPORTED = 0x1;
+ const DYNVC_GFX_PROTOCOL_SUPPORTED = 0x2;
+ const NEGRSP_FLAG_RESERVED = 0x4;
+ const RESTRICTED_ADMIN_MODE_SUPPORTED = 0x8;
+ const REDIRECTED_AUTHENTICATION_MODE_SUPPORTED = 0x10;
+ }
+}
+
+/// rdp-spec, section 2.2.1.1.1
+#[derive(Clone, Debug, PartialEq)]
+pub struct NegotiationFailure {
+ pub code: NegotiationFailureCode,
+}
+
+/// rdp-spec, section 2.2.1.2.2
+#[derive(Clone, Debug, FromPrimitive, PartialEq)]
+pub enum NegotiationFailureCode {
+ SslRequiredByServer = 0x1,
+ SslNotAllowedByServer = 0x2,
+ SslCertNotOnServer = 0x3,
+ InconsistentFlags = 0x4,
+ HybridRequiredByServer = 0x5,
+ SslWithUserAuthRequiredByServer = 0x6,
+}
+
+// ================
+
+/// x224-spec, section 13.7
+#[derive(Clone, Debug, PartialEq)]
+pub struct X223Data {
+ pub child: X223DataChild,
+}
+
+/// variants that an x.223 data message can hold
+#[derive(Clone, Debug, PartialEq)]
+pub enum X223DataChild {
+ McsConnectRequest(McsConnectRequest),
+ McsConnectResponse(McsConnectResponse),
+ Raw(Vec<u8>),
+}
+
+/// t.125-spec, section 7, part 2
+#[derive(Clone, Debug, PartialEq)]
+pub enum T125Type {
+ T125TypeMcsConnectRequest = 0x65, // 101
+ T125TypeMcsConnectResponse = 0x66, // 102
+}
+
+/// rdp-spec, section 2.2.1.3.2
+#[derive(Clone, Debug, PartialEq)]
+pub struct McsConnectRequest {
+ pub children: Vec<McsConnectRequestChild>,
+}
+
+/// variants that an mcs connection message can hold
+#[derive(Clone, Debug, PartialEq)]
+pub enum McsConnectRequestChild {
+ CsClientCore(CsClientCoreData),
+ CsNet(CsNet),
+ CsUnknown(CsUnknown),
+}
+
+/// rdp-spec, section 2.2.1.3.1
+#[derive(Clone, Debug, FromPrimitive, PartialEq)]
+pub enum CsType {
+ Core = 0xc001,
+ Net = 0xc003,
+}
+
+/// rdp-spec, section 2.2.1.3.2
+#[derive(Clone, Debug, PartialEq)]
+pub struct CsClientCoreData {
+ pub version: Option<RdpClientVersion>,
+ pub desktop_width: u16,
+ pub desktop_height: u16,
+ pub color_depth: Option<ColorDepth>,
+ pub sas_sequence: Option<SasSequence>,
+ pub keyboard_layout: u32, // see windows::lcid_to_string
+ pub client_build: windows::OperatingSystem,
+ pub client_name: String,
+ pub keyboard_type: Option<KeyboardType>,
+ pub keyboard_subtype: u32,
+ pub keyboard_function_key: u32,
+ pub ime_file_name: String,
+ // optional fields
+ pub post_beta2_color_depth: Option<PostBeta2ColorDepth>,
+ pub client_product_id: Option<u16>,
+ pub serial_number: Option<u32>,
+ pub high_color_depth: Option<HighColorDepth>,
+ pub supported_color_depth: Option<SupportedColorDepth>,
+ pub early_capability_flags: Option<EarlyCapabilityFlags>,
+ pub client_dig_product_id: Option<String>,
+ pub connection_hint: Option<ConnectionHint>,
+ pub server_selected_protocol: Option<ProtocolFlags>,
+ pub desktop_physical_width: Option<u32>,
+ pub desktop_physical_height: Option<u32>,
+ pub desktop_orientation: Option<DesktopOrientation>,
+ pub desktop_scale_factor: Option<u32>,
+ pub device_scale_factor: Option<u32>,
+}
+
+/// rdp-spec, section 2.2.1.3.2 Client Core Data
+#[derive(Clone, Debug, FromPrimitive, PartialEq)]
+#[allow(non_camel_case_types)]
+pub enum RdpClientVersion {
+ V4 = 0x80001,
+ V5_V8_1 = 0x80004,
+ V10_0 = 0x80005,
+ V10_1 = 0x80006,
+ V10_2 = 0x80007,
+ V10_3 = 0x80008,
+ V10_4 = 0x80009,
+ V10_5 = 0x8000a,
+ V10_6 = 0x8000b,
+ V10_7 = 0x8000c,
+}
+
+/// rdp-spec, section 2.2.1.3.2 Client Core Data
+#[derive(Clone, Debug, FromPrimitive, PartialEq)]
+pub enum ColorDepth {
+ RnsUdColor4Bpp = 0xca00,
+ RnsUdColor8Bpp = 0xca01,
+}
+
+/// rdp-spec, section 2.2.1.3.2 Client Core Data
+#[derive(Clone, Debug, FromPrimitive, PartialEq)]
+pub enum SasSequence {
+ RnsUdSasDel = 0xaa03,
+}
+
+// for keyboard layout, see windows::lcid_to_string
+
+/// rdp-spec, section 2.2.1.3.2 Client Core Data
+#[derive(Clone, Debug, FromPrimitive, PartialEq)]
+pub enum KeyboardType {
+ KbXt = 0x1,
+ KbIco = 0x2,
+ KbAt = 0x3,
+ KbEnhanced = 0x4,
+ Kb1050 = 0x5,
+ Kb9140 = 0x6,
+ KbJapanese = 0x7,
+}
+
+/// rdp-spec, section 2.2.1.3.2 Client Core Data
+#[derive(Clone, Debug, FromPrimitive, PartialEq)]
+pub enum PostBeta2ColorDepth {
+ RnsUdColorNotProvided = 0x0,
+ RnsUdColor4Bpp = 0xca00,
+ RnsUdColor8Bpp = 0xca01,
+ RnsUdColor16Bpp555 = 0xca02,
+ RnsUdColor16Bpp565 = 0xca03,
+ RnsUdColor24Bpp = 0xca04,
+}
+
+/// rdp-spec, section 2.2.1.3.2 Client Core Data
+#[derive(Clone, Debug, FromPrimitive, PartialEq)]
+pub enum HighColorDepth {
+ HighColorNotProvided = 0x0,
+ HighColor4Bpp = 0x4,
+ HighColor8Bpp = 0x8,
+ HighColor15Bpp = 0xf,
+ HighColor16Bpp = 0x10,
+ HighColor24Bpp = 0x18,
+}
+
+// rdp-spec, section 2.2.1.3.2 Client Core Data
+bitflags! {
+ #[derive(Default)]
+ pub struct SupportedColorDepth: u16 {
+ const RNS_UD_24_BPP_SUPPORT = 0x1;
+ const RNS_UD_16_BPP_SUPPORT = 0x2;
+ const RNS_UD_15_BPP_SUPPORT = 0x4;
+ const RNS_UD_32_BPP_SUPPORT = 0x8;
+ }
+}
+
+// rdp-spec, section 2.2.1.3.2 Client Core Data
+bitflags! {
+ #[derive(Default)]
+ pub struct EarlyCapabilityFlags: u16 {
+ const RNS_UD_CS_SUPPORT_ERRINFO_PDF = 0x1;
+ const RNS_UD_CS_WANT_32BPP_SESSION = 0x2;
+ const RNS_UD_CS_SUPPORT_STATUSINFO_PDU = 0x4;
+ const RNS_UD_CS_STRONG_ASYMMETRIC_KEYS = 0x8;
+ const RNS_UD_CS_UNUSED = 0x10;
+ const RNS_UD_CS_VALID_CONNECTION_TYPE = 0x20;
+ const RNS_UD_CS_SUPPORT_MONITOR_LAYOUT_PDU = 0x40;
+ const RNS_UD_CS_SUPPORT_NETCHAR_AUTODETECT = 0x80;
+ const RNS_UD_CS_SUPPORT_DYNVC_GFX_PROTOCOL = 0x100;
+ const RNS_UD_CS_SUPPORT_DYNAMIC_TIME_ZONE = 0x200;
+ const RNS_UD_CS_SUPPORT_HEARTBEAT_PDU = 0x400;
+ }
+}
+
+/// rdp-spec, section 2.2.1.3.2 Client Core Data, `connectionType`
+#[derive(Clone, Debug, FromPrimitive, PartialEq)]
+pub enum ConnectionHint {
+ ConnectionHintNotProvided = 0x0,
+ ConnectionHintModem = 0x1,
+ ConnectionHintBroadbandLow = 0x2,
+ ConnectionHintSatellite = 0x3,
+ ConnectionHintBroadbandHigh = 0x4,
+ ConnectionHintWan = 0x5,
+ ConnectionHintLan = 0x6,
+ ConnectionHintAutoDetect = 0x7,
+}
+
+/// rdp-spec, section 2.2.1.3.2 Client Core Data
+#[derive(Clone, Copy, Debug, FromPrimitive, PartialEq)]
+pub enum DesktopOrientation {
+ OrientationLandscape = 0,
+ OrientationPortrait = 90, // 0x5a
+ OrientationLandscapeFlipped = 180, // 0xb4
+ OrientationPortraitFlipped = 270, // 0x10e
+}
+
+/// rdp-spec, section 2.2.1.3.4
+#[derive(Clone, Debug, PartialEq)]
+pub struct CsNet {
+ pub channels: Vec<String>,
+}
+
+/// generic structure
+/// cf. rdp-spec, section 2.2.1.3.4
+#[derive(Clone, Debug, PartialEq)]
+pub struct CsUnknown {
+ pub typ: u16,
+ pub data: Vec<u8>,
+}
+
+/// rdp-spec, section 2.2.1.4
+#[derive(Clone, Debug, PartialEq)]
+pub struct McsConnectResponse {}
+
+// ==================
+
+/// parser for t.123 and children
+/// t.123-spec, section 8
+pub fn parse_t123_tpkt(input: &[u8]) -> IResult<&[u8], T123Tpkt> {
+ let (i1, _version) =
+ verify!(input, be_u8, |x| x == TpktVersion::T123 as u8)?;
+ let (i2, _reserved) = try_parse!(i1, be_u8);
+ // less u8, u8, u16
+ let (i3, sz) = map_opt!(i2, be_u16, |x: u16| x.checked_sub(4))?;
+ let (i4, data) = take!(i3, sz)?;
+
+ let opt1: Option<T123TpktChild> = {
+ match opt!(data, parse_x224_connection_request_class_0) {
+ Ok((_remainder, opt)) => match opt {
+ Some(x) => Some(T123TpktChild::X224ConnectionRequest(x)),
+ None => None,
+ },
+ Err(e) => return Err(e),
+ }
+ };
+
+ let opt2: Option<T123TpktChild> = match opt1 {
+ Some(x) => Some(x),
+ None => match opt!(data, parse_x224_connection_confirm_class_0) {
+ Ok((_remainder, opt)) => match opt {
+ Some(x) => Some(T123TpktChild::X224ConnectionConfirm(x)),
+ None => None,
+ },
+ Err(e) => return Err(e),
+ },
+ };
+
+ let opt3: Option<T123TpktChild> = match opt2 {
+ Some(x) => Some(x),
+ None => match opt!(data, parse_x223_data_class_0) {
+ Ok((_remainder, opt)) => match opt {
+ Some(x) => Some(T123TpktChild::Data(x)),
+ None => None,
+ },
+ Err(e) => return Err(e),
+ },
+ };
+ let child: T123TpktChild = match opt3 {
+ Some(x) => x,
+ None => T123TpktChild::Raw(data.to_vec()),
+ };
+
+ return Ok((i4, T123Tpkt { child }));
+}
+
+/// rdp-spec, section 2.2.1.1
+fn parse_x224_connection_request(
+ input: &[u8],
+) -> IResult<&[u8], X224ConnectionRequest> {
+ let (i1, length) = verify!(input, be_u8, |x| x != 0xff)?; // 0xff is reserved
+ let (i2, cr_cdt) = bits!(
+ i1,
+ tuple!(
+ verify!(take_bits!(u8, 4), |x| x
+ == X224Type::ConnectionRequest as u8),
+ verify!(take_bits!(u8, 4), |x| x == 0 || x == 1)
+ )
+ )?;
+ let (i3, dst_ref) = verify!(i2, be_u16, |x| x == 0)?;
+ let (i4, src_ref) = try_parse!(i3, be_u16);
+ let (i5, class_options) = bits!(
+ i4,
+ tuple!(
+ verify!(take_bits!(u8, 4), |x| x <= 4),
+ verify!(take_bits!(u8, 4), |x| x <= 3)
+ )
+ )?;
+ // less cr_cdt (u8), dst_ref (u16), src_ref (u16), class_options (u8)
+ let (i6, sz) = expr_opt!(i5, length.checked_sub(6))?;
+
+ //
+ // optionally find cookie and/or negotiation request
+ //
+
+ let (i7, data) = {
+ if sz > 0 {
+ take!(i6, sz)?
+ } else {
+ (i6, &[][..])
+ }
+ };
+
+ let (j1, cookie) = {
+ if data.len() > 0 {
+ match opt!(data, parse_rdp_cookie) {
+ Ok((remainder, opt)) => (remainder, opt),
+ Err(e) => return Err(e),
+ }
+ } else {
+ (&[][..], None)
+ }
+ };
+
+ let (j2, negotiation_request) = {
+ if j1.len() > 0 {
+ match opt!(j1, parse_negotiation_request) {
+ Ok((remainder, opt)) => (remainder, opt),
+ Err(e) => return Err(e),
+ }
+ } else {
+ (&[][..], None)
+ }
+ };
+
+ return Ok((
+ i7,
+ X224ConnectionRequest {
+ cdt: cr_cdt.1,
+ dst_ref,
+ src_ref,
+ class: class_options.0,
+ options: class_options.1,
+ cookie,
+ negotiation_request,
+ data: j2.to_vec(),
+ },
+ ));
+}
+
+/// rdp-spec, section 2.2.1.1
+/// "An X.224 Class 0 Connection Request TPDU, as specified in [X224] section 13.3."
+fn parse_x224_connection_request_class_0(
+ input: &[u8],
+) -> IResult<&[u8], X224ConnectionRequest> {
+ let (i1, x224) = try_parse!(input, parse_x224_connection_request);
+ if x224.class == 0 && x224.options == 0 {
+ Ok((i1, x224))
+ } else {
+ Err(nom::Err::Error(error_position!(
+ input,
+ ErrorKind::Custom(RDP_NOT_X224_CLASS_0_ERROR)
+ )))
+ }
+}
+
+// rdp-spec, section 2.2.1.1.1
+fn parse_rdp_cookie(input: &[u8]) -> IResult<&[u8], RdpCookie> {
+ do_parse! {
+ input,
+ _key: verify!(
+ take!(8),
+ |x| x == b"Cookie: ")
+ >> _name: verify!(
+ take!(9),
+ |x| x == b"mstshash=")
+ >> bytes: take_until_and_consume!("\r\n")
+ >> s: map_res!(value!(bytes), std::str::from_utf8)
+ >> (RdpCookie{ mstshash: String::from(s) })
+ }
+}
+
+// rdp-spec, section 2.2.1.1.1
+fn parse_negotiation_request(
+ input: &[u8],
+) -> IResult<&[u8], NegotiationRequest> {
+ do_parse! {
+ input,
+ _typ: verify!(
+ le_u8,
+ |x| x == X224ConnectionRequestType::NegotiationRequest as u8)
+ >> flags: map_opt!(
+ le_u8,
+ NegotiationRequestFlags::from_bits)
+ // u8, u8, u16, and u32 give _length of 8
+ >> _length: verify!(
+ le_u16,
+ |x| x == 8)
+ >> protocols: map_opt!(
+ le_u32,
+ ProtocolFlags::from_bits)
+ >> (NegotiationRequest { flags, protocols })
+ }
+}
+
+/// rdp-spec, section 2.2.1.2
+/// x.224-spec, section 13.3
+fn parse_x224_connection_confirm(
+ input: &[u8],
+) -> IResult<&[u8], X224ConnectionConfirm> {
+ let (i1, length) = verify!(input, be_u8, |x| x != 0xff)?; // 0xff is reserved
+ let (i2, cr_cdt) = bits!(
+ i1,
+ tuple!(
+ verify!(take_bits!(u8, 4), |x| x
+ == X224Type::ConnectionConfirm as u8),
+ verify!(take_bits!(u8, 4), |x| x == 0 || x == 1)
+ )
+ )?;
+ let (i3, dst_ref) = verify!(i2, be_u16, |x| x == 0)?;
+ let (i4, src_ref) = try_parse!(i3, be_u16);
+ let (i5, class_options) = bits!(
+ i4,
+ tuple!(
+ verify!(take_bits!(u8, 4), |x| x <= 4),
+ verify!(take_bits!(u8, 4), |x| x <= 3)
+ )
+ )?;
+
+ // less cr_cdt (u8), dst_ref (u16), src_ref (u16), class_options (u8)
+ let (i6, sz) = expr_opt!(i5, length.checked_sub(6))?;
+
+ // a negotiation message from the server might be absent (sz == 0)
+ let (i7, negotiation_from_server) = {
+ if sz > 0 {
+ let (i7, data) = take!(i6, sz)?;
+
+ // it will be one of a response message or a failure message
+ let opt1: Option<NegotiationFromServer> =
+ match opt!(data, parse_negotiation_response) {
+ Ok((_remainder, opt)) => match opt {
+ Some(x) => Some(NegotiationFromServer::Response(x)),
+ None => None,
+ },
+ Err(e) => return Err(e),
+ };
+ let opt2: Option<NegotiationFromServer> = match opt1 {
+ Some(x) => Some(x),
+ None => match opt!(data, parse_negotiation_failure) {
+ Ok((_remainder, opt)) => match opt {
+ Some(x) => Some(NegotiationFromServer::Failure(x)),
+ None => None,
+ },
+ Err(e) => return Err(e),
+ },
+ };
+ (i7, opt2)
+ } else {
+ (i6, None)
+ }
+ };
+
+ return Ok((
+ i7,
+ X224ConnectionConfirm {
+ cdt: cr_cdt.1,
+ dst_ref,
+ src_ref,
+ class: class_options.0,
+ options: class_options.1,
+ negotiation_from_server,
+ },
+ ));
+}
+
+/// rdp-spec, section 2.2.1.2
+/// "An X.224 Class 0 Connection Confirm TPDU, as specified in [X224] section 13.4."
+fn parse_x224_connection_confirm_class_0(
+ input: &[u8],
+) -> IResult<&[u8], X224ConnectionConfirm> {
+ let (i1, x224) = try_parse!(input, parse_x224_connection_confirm);
+ if x224.class == 0 && x224.options == 0 {
+ Ok((i1, x224))
+ } else {
+ // x.224, but not a class 0 x.224 message
+ Err(nom::Err::Error(error_position!(
+ input,
+ ErrorKind::Custom(RDP_NOT_X224_CLASS_0_ERROR)
+ )))
+ }
+}
+
+// rdp-spec, section 2.2.1.1.1
+fn parse_negotiation_response(
+ input: &[u8],
+) -> IResult<&[u8], NegotiationResponse> {
+ do_parse! {
+ input,
+ _typ: verify!(
+ le_u8,
+ |x| x == X224ConnectionRequestType::NegotiationResponse as u8)
+ >> flags: map_opt!(
+ le_u8,
+ NegotiationResponseFlags::from_bits)
+ // u8, u8, u16, and u32 give _length of 8
+ >> _length: verify!(
+ le_u16,
+ |x| x == 8)
+ >> protocol: map_opt!(
+ le_u32,
+ num::FromPrimitive::from_u32)
+ >> (NegotiationResponse { flags, protocol })
+ }
+}
+
+// rdp-spec, section 2.2.1.1.1
+fn parse_negotiation_failure(
+ input: &[u8],
+) -> IResult<&[u8], NegotiationFailure> {
+ do_parse! {
+ input,
+ _typ: verify!(
+ le_u8,
+ |x| x == X224ConnectionRequestType::NegotiationFailure as u8)
+ >> _flags: le_u8
+ // u8, u8, u16, and u32 give _length of 8
+ >> _length: verify!(
+ le_u16,
+ |x| x == 8)
+ >> code: map_opt!(
+ le_u32,
+ num::FromPrimitive::from_u32)
+ >> (NegotiationFailure { code })
+ }
+}
+
+/// x224-spec, section 13.7
+fn parse_x223_data_class_0(input: &[u8]) -> IResult<&[u8], X223Data> {
+ let (i1, _length) = verify!(input, be_u8, |x| x == 2)?;
+ let (i2, _dt_x_roa) = bits!(
+ i1,
+ tuple!(
+ verify!(take_bits!(u8, 4), |x| x == 0xf),
+ verify!(take_bits!(u8, 3), |x| x == 0),
+ verify!(take_bits!(u8, 1), |x| x == 0)
+ )
+ )?;
+ let (i3, _eot) = verify!(i2, be_u8, |x| x == 0x80)?;
+
+ //
+ // optionally find exactly one of the child messages
+ //
+
+ let opt1: Option<X223DataChild> = match opt!(i3, parse_mcs_connect) {
+ Ok((_remainder, opt)) => match opt {
+ Some(x) => Some(X223DataChild::McsConnectRequest(x)),
+ None => None,
+ },
+ Err(e) => return Err(e),
+ };
+
+ let opt2: Option<X223DataChild> = match opt1 {
+ Some(x) => Some(x),
+ None => match opt!(i3, parse_mcs_connect_response) {
+ Ok((_remainder, opt)) => match opt {
+ Some(x) => Some(X223DataChild::McsConnectResponse(x)),
+ None => None,
+ },
+ Err(e) => return Err(e),
+ },
+ };
+
+ let child: X223DataChild = match opt2 {
+ Some(x) => x,
+ None => X223DataChild::Raw(i3.to_vec()),
+ };
+
+ return Ok((&[], X223Data { child }));
+}
+
+/// rdp-spec, section 2.2.1.3.2
+fn parse_mcs_connect(input: &[u8]) -> IResult<&[u8], McsConnectRequest> {
+ let (i1, _ber_type) = verify!(
+ input,
+ le_u8,
+ // BER: 0b01=application, 0b1=non-primitive, 0b11111
+ |x| x == 0x7f
+ )?;
+ let (i2, _t125_type) = verify!(i1, le_u8, |x| x
+ == T125Type::T125TypeMcsConnectRequest as u8)?;
+
+ // skip to, and consume, H.221 client-to-server key
+ let (i3, _skipped) = take_until_and_consume!(i2, "Duca")?;
+
+ let (i4, data) = length_data!(i3, parse_per_length_determinant)?;
+ let mut remainder: &[u8] = data;
+ let mut children = Vec::new();
+
+ // repeatedly attempt to parse optional CsClientCoreData, CsNet, and CsUnknown
+ // until data buffer is exhausted
+ loop {
+ remainder = match opt!(remainder, parse_cs_client_core_data) {
+ Ok((rem, opt)) => match opt {
+ // found CsClientCoreData
+ Some(core_data) => {
+ children
+ .push(McsConnectRequestChild::CsClientCore(core_data));
+ rem
+ }
+ None => match opt!(remainder, parse_cs_net) {
+ // found CsNet
+ Ok((rem, opt)) => match opt {
+ Some(net) => {
+ children.push(McsConnectRequestChild::CsNet(net));
+ rem
+ }
+ None => {
+ match opt!(remainder, parse_cs_unknown) {
+ // was able to parse CsUnknown
+ Ok((rem, opt)) => {
+ match opt {
+ Some(unknown) => {
+ children.push(McsConnectRequestChild::CsUnknown(unknown));
+ rem
+ }
+ None => {
+ break;
+ }
+ }
+ }
+ Err(nom::Err::Incomplete(i)) => {
+ return Err(nom::Err::Incomplete(i))
+ }
+ Err(nom::Err::Failure(_))
+ | Err(nom::Err::Error(_)) => break,
+ }
+ }
+ },
+ Err(nom::Err::Incomplete(i)) => {
+ return Err(nom::Err::Incomplete(i))
+ }
+ Err(nom::Err::Failure(_)) | Err(nom::Err::Error(_)) => {
+ break
+ }
+ },
+ },
+ Err(nom::Err::Incomplete(i)) => {
+ return Err(nom::Err::Incomplete(i))
+ }
+ Err(nom::Err::Failure(_)) | Err(nom::Err::Error(_)) => break,
+ };
+ if remainder.len() == 0 {
+ break;
+ }
+ }
+
+ return Ok((i4, McsConnectRequest { children }));
+}
+
+/// rdp-spec, section 2.2.1.3.2
+fn parse_cs_client_core_data(input: &[u8]) -> IResult<&[u8], CsClientCoreData> {
+ let (i1, _typ) = verify!(input, le_u16, |x| x == CsType::Core as u16)?;
+ // less u16, u16
+ let (i2, sz) = map_opt!(i1, le_u16, |x: u16| x.checked_sub(4))?;
+ let (i3, data) = take!(i2, sz)?;
+ let (j1, version) = map!(data, le_u32, num::FromPrimitive::from_u32)?;
+ let (j2, desktop_width) = try_parse!(j1, le_u16);
+ let (j3, desktop_height) = try_parse!(j2, le_u16);
+ let (j4, color_depth) = map!(j3, le_u16, num::FromPrimitive::from_u16)?;
+ let (j5, sas_sequence) = map!(j4, le_u16, num::FromPrimitive::from_u16)?;
+ let (j6, keyboard_layout) = try_parse!(j5, le_u32);
+ let (j7, client_build) = map!(j6, le_u32, windows::build_number_to_os)?;
+ let (j8, client_name) = map_res!(j7, take!(32), le_slice_to_string)?;
+ let (j9, keyboard_type) = map!(j8, le_u32, num::FromPrimitive::from_u32)?;
+ let (j10, keyboard_subtype) = try_parse!(j9, le_u32);
+ let (j11, keyboard_function_key) = try_parse!(j10, le_u32);
+ let (j12, ime_file_name) = map_res!(j11, take!(64), le_slice_to_string)?;
+
+ //
+ // optional fields below (but each requires the previous)
+ //
+
+ let (j13, post_beta2_color_depth) =
+ match opt!(j12, map_opt!(le_u16, num::FromPrimitive::from_u16)) {
+ Ok((rem, obj)) => (rem, obj),
+ _ => (j12, None),
+ };
+
+ let (j14, client_product_id) = match post_beta2_color_depth {
+ None => (j13, None),
+ Some(_) => match opt!(j13, le_u16) {
+ Ok((rem, obj)) => (rem, obj),
+ _ => (j13, None),
+ },
+ };
+
+ let (j15, serial_number) = match client_product_id {
+ None => (j14, None),
+ Some(_) => match opt!(j14, le_u32) {
+ Ok((rem, obj)) => (rem, obj),
+ _ => (j14, None),
+ },
+ };
+
+ let (j16, high_color_depth) = match serial_number {
+ None => (j15, None),
+ Some(_) => {
+ match opt!(j15, map_opt!(le_u16, num::FromPrimitive::from_u16)) {
+ Ok((rem, obj)) => (rem, obj),
+ _ => (j15, None),
+ }
+ }
+ };
+
+ let (j17, supported_color_depth) = match high_color_depth {
+ None => (j16, None),
+ Some(_) => {
+ match opt!(j16, map_opt!(le_u16, SupportedColorDepth::from_bits)) {
+ Ok((rem, obj)) => (rem, obj),
+ _ => (j16, None),
+ }
+ }
+ };
+
+ let (j18, early_capability_flags) = match supported_color_depth {
+ None => (j17, None),
+ Some(_) => {
+ match opt!(j17, map_opt!(le_u16, EarlyCapabilityFlags::from_bits)) {
+ Ok((rem, obj)) => (rem, obj),
+ _ => (j17, None),
+ }
+ }
+ };
+
+ let (j19, client_dig_product_id) = match early_capability_flags {
+ None => (j18, None),
+ Some(_) => match opt!(j18, map_res!(take!(64), le_slice_to_string)) {
+ Ok((rem, obj)) => (rem, obj),
+ _ => (j18, None),
+ },
+ };
+
+ let (j20, connection_hint) = match client_dig_product_id {
+ None => (j19, None),
+ Some(_) => {
+ match opt!(j19, map_opt!(le_u8, num::FromPrimitive::from_u8)) {
+ Ok((rem, obj)) => (rem, obj),
+ _ => (j19, None),
+ }
+ }
+ };
+
+ let (j21, pad) = match connection_hint {
+ None => (j20, None),
+ Some(_) => match opt!(j20, take!(1)) {
+ Ok((rem, obj)) => (rem, obj),
+ _ => (j20, None),
+ },
+ };
+
+ let (j22, server_selected_protocol) = match pad {
+ None => (j21, None),
+ Some(_) => {
+ match opt!(j21, map_opt!(le_u32, ProtocolFlags::from_bits)) {
+ Ok((rem, obj)) => (rem, obj),
+ _ => (j21, None),
+ }
+ }
+ };
+
+ let (j23, desktop_physical_width) = match server_selected_protocol {
+ None => (j22, None),
+ Some(_) => match opt!(j22, map_opt!(le_u32, millimeters_to_opt)) {
+ Ok((rem, obj)) => (rem, obj),
+ _ => (j22, None),
+ },
+ };
+
+ let (j24, desktop_physical_height) = match desktop_physical_width {
+ None => (j23, None),
+ Some(_) => match opt!(j23, map_opt!(le_u32, millimeters_to_opt)) {
+ Ok((rem, obj)) => (rem, obj),
+ _ => (j23, None),
+ },
+ };
+
+ let (j25, desktop_orientation) = match desktop_physical_height {
+ None => (j24, None),
+ Some(_) => {
+ match opt!(j24, map_opt!(le_u16, num::FromPrimitive::from_u16)) {
+ Ok((rem, obj)) => (rem, obj),
+ _ => (j24, None),
+ }
+ }
+ };
+
+ let (j26, desktop_scale_factor) = match desktop_orientation {
+ None => (j25, None),
+ Some(_) => match opt!(j25, map_opt!(le_u32, desktop_scale_to_opt)) {
+ Ok((rem, obj)) => (rem, obj),
+ _ => (j25, None),
+ },
+ };
+
+ let (_j27, device_scale_factor) = match desktop_scale_factor {
+ None => (j26, None),
+ Some(_) => match opt!(j26, map_opt!(le_u32, device_scale_to_opt)) {
+ Ok((rem, obj)) => (rem, obj),
+ _ => (j26, None),
+ },
+ };
+
+ return Ok((
+ i3,
+ CsClientCoreData {
+ version,
+ desktop_width,
+ desktop_height,
+ color_depth,
+ sas_sequence,
+ keyboard_layout,
+ client_build,
+ client_name,
+ keyboard_type,
+ keyboard_subtype,
+ keyboard_function_key,
+ ime_file_name,
+ post_beta2_color_depth,
+ client_product_id,
+ serial_number,
+ high_color_depth,
+ supported_color_depth,
+ early_capability_flags,
+ client_dig_product_id,
+ connection_hint,
+ server_selected_protocol,
+ desktop_physical_width,
+ desktop_physical_height,
+ desktop_orientation,
+ desktop_scale_factor,
+ device_scale_factor,
+ },
+ ));
+}
+
+/// rdp-spec, section 2.2.1.3.4
+fn parse_cs_net(input: &[u8]) -> IResult<&[u8], CsNet> {
+ let (i1, _typ) = verify!(input, le_u16, |x| x == CsType::Net as u16)?;
+ // less _typ (u16), this length indicator (u16), count (u32)
+ let (i2, sz) = map_opt!(i1, le_u16, |x: u16| x.checked_sub(8))?;
+ let (i3, count) = try_parse!(i2, le_u32);
+ let (i4, data) = take!(i3, sz)?;
+
+ let mut remainder: &[u8] = data;
+ let mut channels = Vec::new();
+
+ for _index in 0..count {
+ // a channel name is 8 bytes, section 2.2.1.3.4.1
+ let (j1, name) = map_res!(remainder, take!(8), utf7_slice_to_string)?;
+ channels.push(name);
+ // options (u32) are discarded for now
+ let (j2, _options) = try_parse!(j1, le_u32);
+ remainder = j2;
+ }
+
+ return Ok((i4, CsNet { channels }));
+}
+
+// generic CS structure parse
+// cf. rdp-spec, section 2.2.1.3.4
+fn parse_cs_unknown(input: &[u8]) -> IResult<&[u8], CsUnknown> {
+ do_parse! {
+ input,
+ typ: map_opt!(
+ le_u16,
+ |x| {
+ let opt: Option<CsType> = num::FromPrimitive::from_u16(x);
+ match opt {
+ // an unknown type must not be present in CsType
+ Some(_) => None,
+ None => Some(x),
+ }
+ })
+ // less u16, u16
+ >> sz: map_opt!(le_u16, |x: u16| x.checked_sub(4))
+ >> data: take!(sz)
+ >> (CsUnknown { typ, data: data.to_vec() })
+ }
+}
+
+// rdp-spec, section 2.2.1.4
+fn parse_mcs_connect_response(
+ input: &[u8],
+) -> IResult<&[u8], McsConnectResponse> {
+ do_parse! {
+ input,
+ _ber_type: verify!(
+ le_u8,
+ // BER: 0b01=application, 0b1=non-primitive, 0b11111
+ |x| x == 0x7f)
+ >> _t125_type: verify!(
+ le_u8,
+ |x| x == T125Type::T125TypeMcsConnectResponse as u8)
+ >> (McsConnectResponse {})
+ }
+}
+
+#[cfg(test)]
+mod tests_cookie_21182 {
+ use rdp::parser::*;
+
+ static BYTES: [u8; 37] = [
+ 0x03, 0x00, 0x00, 0x25, 0x20, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43,
+ 0x6f, 0x6f, 0x6b, 0x69, 0x65, 0x3a, 0x20, 0x6d, 0x73, 0x74, 0x73, 0x68,
+ 0x61, 0x73, 0x68, 0x3d, 0x75, 0x73, 0x65, 0x72, 0x31, 0x32, 0x33, 0x0d,
+ 0x0a,
+ ];
+
+ #[test]
+ fn test_t123_x224_cookie() {
+ let t123_bytes = &BYTES[..];
+ let t123_tpkt: T123Tpkt = T123Tpkt {
+ child: T123TpktChild::X224ConnectionRequest(
+ X224ConnectionRequest {
+ cdt: 0,
+ dst_ref: 0,
+ src_ref: 0,
+ class: 0,
+ options: 0,
+ cookie: Some(RdpCookie {
+ mstshash: String::from("user123"),
+ }),
+ negotiation_request: None,
+ data: Vec::new(),
+ },
+ ),
+ };
+ assert_eq!(Ok((&[][..], t123_tpkt)), parse_t123_tpkt(t123_bytes));
+ }
+}
+
+#[cfg(test)]
+mod tests_negotiate_49350 {
+ use rdp::parser::*;
+
+ static BYTES: [u8; 20] = [
+ 0x03, 0x00, 0x00, 0x13, 0x0e, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
+ ];
+ static TPKT_BEGIN: usize = 0;
+ static X224_BEGIN: usize = TPKT_BEGIN + 4;
+ static NEG_REQ_BEGIN: usize = X224_BEGIN + 7;
+ static NEG_REQ_END: usize = NEG_REQ_BEGIN + 8;
+ static X224_END: usize = NEG_REQ_END;
+ static TPKT_END: usize = X224_END;
+ static PADDING_BEGIN: usize = TPKT_END;
+
+ #[test]
+ fn test_t123_x224_negotiate() {
+ let t123_bytes = &BYTES[TPKT_BEGIN..];
+ let t123_tpkt: T123Tpkt = T123Tpkt {
+ child: T123TpktChild::X224ConnectionRequest(
+ X224ConnectionRequest {
+ cdt: 0,
+ dst_ref: 0,
+ src_ref: 0,
+ class: 0,
+ options: 0,
+ cookie: None,
+ negotiation_request: Some(NegotiationRequest {
+ flags: NegotiationRequestFlags::empty(),
+ protocols: ProtocolFlags::PROTOCOL_RDP,
+ }),
+ data: Vec::new(),
+ },
+ ),
+ };
+ assert_eq!(
+ Ok((&BYTES[PADDING_BEGIN..][..], t123_tpkt)),
+ parse_t123_tpkt(t123_bytes)
+ )
+ }
+}
+
+#[cfg(test)]
+mod tests_core_49350 {
+ use rdp::parser::*;
+
+ static BYTES: [u8; 429] = [
+ 0x03, 0x00, 0x01, 0xac, 0x02, 0xf0, 0x80, 0x7f, 0x65, 0x82, 0x01, 0xa0,
+ 0x04, 0x01, 0x01, 0x04, 0x01, 0x01, 0x01, 0x01, 0xff, 0x30, 0x19, 0x02,
+ 0x01, 0x22, 0x02, 0x01, 0x02, 0x02, 0x01, 0x00, 0x02, 0x01, 0x01, 0x02,
+ 0x01, 0x00, 0x02, 0x01, 0x01, 0x02, 0x02, 0xff, 0xff, 0x02, 0x01, 0x02,
+ 0x30, 0x19, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x02,
+ 0x01, 0x01, 0x02, 0x01, 0x00, 0x02, 0x01, 0x01, 0x02, 0x02, 0x04, 0x20,
+ 0x02, 0x01, 0x02, 0x30, 0x1c, 0x02, 0x02, 0xff, 0xff, 0x02, 0x02, 0xfc,
+ 0x17, 0x02, 0x02, 0xff, 0xff, 0x02, 0x01, 0x01, 0x02, 0x01, 0x00, 0x02,
+ 0x01, 0x01, 0x02, 0x02, 0xff, 0xff, 0x02, 0x01, 0x02, 0x04, 0x82, 0x01,
+ 0x3f, 0x00, 0x05, 0x00, 0x14, 0x7c, 0x00, 0x01, 0x81, 0x36, 0x00, 0x08,
+ 0x00, 0x10, 0x00, 0x01, 0xc0, 0x00, 0x44, 0x75, 0x63, 0x61, 0x81, 0x28,
+ 0x01, 0xc0, 0xd8, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x05, 0x00, 0x03,
+ 0x01, 0xca, 0x03, 0xaa, 0x09, 0x04, 0x00, 0x00, 0x71, 0x17, 0x00, 0x00,
+ 0x53, 0x00, 0x45, 0x00, 0x52, 0x00, 0x56, 0x00, 0x45, 0x00, 0x52, 0x00,
+ 0x2d, 0x00, 0x58, 0x00, 0x59, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0xca, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0f, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0xc0, 0x0c, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0xc0, 0x0c, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x03, 0xc0, 0x38, 0x00, 0x04, 0x00, 0x00, 0x00, 0x72, 0x64, 0x70, 0x64,
+ 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x72, 0x64, 0x70, 0x73,
+ 0x6e, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x64, 0x72, 0x64, 0x79,
+ 0x6e, 0x76, 0x63, 0x00, 0x00, 0x00, 0x80, 0xc0, 0x63, 0x6c, 0x69, 0x70,
+ 0x72, 0x64, 0x72, 0x00, 0x00, 0x00, 0xa0, 0xc0, 0xff,
+ ];
+ static TPKT_BEGIN: usize = 0;
+ static X223_BEGIN: usize = TPKT_BEGIN + 4;
+ static MCS_CONNECT_BEGIN: usize = X223_BEGIN + 3;
+ static MCS_CONNECT_END: usize = MCS_CONNECT_BEGIN + 421;
+ static X223_END: usize = MCS_CONNECT_END;
+ static TPKT_END: usize = X223_END;
+ static PADDING_BEGIN: usize = TPKT_END;
+
+ #[test]
+ fn test_t123_x223_connect_core() {
+ let t123_bytes = &BYTES[TPKT_BEGIN..];
+ let core_data = CsClientCoreData {
+ version: Some(RdpClientVersion::V5_V8_1),
+ desktop_width: 1280,
+ desktop_height: 768,
+ color_depth: Some(ColorDepth::RnsUdColor8Bpp),
+ sas_sequence: Some(SasSequence::RnsUdSasDel),
+ keyboard_layout: 0x409,
+ client_build: windows::OperatingSystem {
+ build: windows::Build::Vista_6001,
+ suffix: windows::Suffix::Sp1,
+ },
+ client_name: String::from("SERVER-XYZ"),
+ keyboard_type: Some(KeyboardType::KbEnhanced),
+ keyboard_subtype: 0,
+ keyboard_function_key: 12,
+ ime_file_name: String::from(""),
+ post_beta2_color_depth: Some(PostBeta2ColorDepth::RnsUdColor8Bpp),
+ client_product_id: Some(1),
+ serial_number: Some(0),
+ high_color_depth: Some(HighColorDepth::HighColor8Bpp),
+ supported_color_depth: Some(
+ SupportedColorDepth::RNS_UD_15_BPP_SUPPORT
+ | SupportedColorDepth::RNS_UD_16_BPP_SUPPORT
+ | SupportedColorDepth::RNS_UD_24_BPP_SUPPORT
+ | SupportedColorDepth::RNS_UD_32_BPP_SUPPORT,
+ ),
+ early_capability_flags: Some(
+ EarlyCapabilityFlags::RNS_UD_CS_SUPPORT_ERRINFO_PDF
+ | EarlyCapabilityFlags::RNS_UD_CS_STRONG_ASYMMETRIC_KEYS,
+ ),
+ client_dig_product_id: Some(String::from("")),
+ connection_hint: Some(ConnectionHint::ConnectionHintNotProvided),
+ server_selected_protocol: Some(ProtocolFlags::PROTOCOL_RDP),
+ desktop_physical_width: None,
+ desktop_physical_height: None,
+ desktop_orientation: None,
+ desktop_scale_factor: None,
+ device_scale_factor: None,
+ };
+ let mut children = Vec::new();
+ children.push(McsConnectRequestChild::CsClientCore(core_data));
+ children.push(McsConnectRequestChild::CsUnknown(CsUnknown {
+ typ: 0xc004,
+ data: BYTES[0x160..0x160 + 0x8].to_vec(),
+ }));
+ children.push(McsConnectRequestChild::CsUnknown(CsUnknown {
+ typ: 0xc002,
+ data: BYTES[0x16c..0x16c + 0x8].to_vec(),
+ }));
+ let mut channels = Vec::new();
+ channels.push(String::from("rdpdr"));
+ channels.push(String::from("rdpsnd"));
+ channels.push(String::from("drdynvc"));
+ channels.push(String::from("cliprdr"));
+ children.push(McsConnectRequestChild::CsNet(CsNet { channels }));
+ let t123_tpkt: T123Tpkt = T123Tpkt {
+ child: T123TpktChild::Data(X223Data {
+ child: X223DataChild::McsConnectRequest(McsConnectRequest {
+ children,
+ }),
+ }),
+ };
+ assert_eq!(
+ Ok((&BYTES[PADDING_BEGIN..][..], t123_tpkt)),
+ parse_t123_tpkt(t123_bytes)
+ );
+ }
+}
+
+#[cfg(test)]
+mod tests_x223_response_49350 {
+ use rdp::parser::*;
+
+ // changed offset 9 from 0x65 to 0x66 so it is no longer an mcs connect
+ static BYTES: [u8; 9] =
+ [0x03, 0x00, 0x00, 0x09, 0x02, 0xf0, 0x80, 0x7f, 0x66];
+
+ #[test]
+ fn test_x223_response() {
+ let t123_bytes = &BYTES[..];
+ assert_eq!(
+ Ok((
+ &[][..],
+ T123Tpkt {
+ child: T123TpktChild::Data(X223Data {
+ child: X223DataChild::McsConnectResponse(
+ McsConnectResponse {}
+ ),
+ })
+ }
+ )),
+ parse_t123_tpkt(t123_bytes)
+ )
+ }
+}
+
+#[cfg(test)]
+mod tests_t123_raw_49350 {
+ use rdp::parser::*;
+
+ // changed offset 4 from 0x02 to 0x03 so it is no longer an X223 data object
+ static BYTES: [u8; 9] =
+ [0x03, 0x00, 0x00, 0x09, 0x03, 0xf0, 0x80, 0x7f, 0x65];
+
+ #[test]
+ fn test_t123_raw() {
+ let t123_bytes = &BYTES[..];
+ assert_eq!(
+ Ok((
+ &[][..],
+ T123Tpkt {
+ child: T123TpktChild::Raw(BYTES[4..].to_vec())
+ }
+ )),
+ parse_t123_tpkt(t123_bytes)
+ )
+ }
+}
+
+#[cfg(test)]
+mod tests_x224_raw_49350 {
+ use rdp::parser::*;
+
+ // changed offset 11 from 0x01 to 0x02 so it is not a known X224 payload type
+ static BYTES: [u8; 19] = [
+ 0x03, 0x00, 0x00, 0x13, 0x0e, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
+ ];
+
+ #[test]
+ fn test_x224_raw() {
+ let t123_bytes = &BYTES[..];
+ assert_eq!(
+ Ok((
+ &[][..],
+ T123Tpkt {
+ child: T123TpktChild::X224ConnectionRequest(
+ X224ConnectionRequest {
+ cdt: 0,
+ dst_ref: 0,
+ src_ref: 0,
+ class: 0,
+ options: 0,
+ cookie: None,
+ negotiation_request: None,
+ data: BYTES[11..].to_vec(),
+ }
+ )
+ }
+ )),
+ parse_t123_tpkt(t123_bytes)
+ )
+ }
+}
+
+#[cfg(test)]
+mod tests_x223_raw_49350 {
+ use rdp::parser::*;
+
+ // changed offset 9 from 0x65 to 0xff so it is no longer an mcs connect
+ static BYTES: [u8; 9] =
+ [0x03, 0x00, 0x00, 0x09, 0x02, 0xf0, 0x80, 0x7f, 0xff];
+
+ #[test]
+ fn test_x223_raw() {
+ let t123_bytes = &BYTES[..];
+ assert_eq!(
+ Ok((
+ &[][..],
+ T123Tpkt {
+ child: T123TpktChild::Data(X223Data {
+ child: X223DataChild::Raw(BYTES[7..].to_vec()),
+ })
+ }
+ )),
+ parse_t123_tpkt(t123_bytes)
+ )
+ }
+}
+
+#[cfg(test)]
+mod tests_negotiate_incomplete_49350 {
+ use nom;
+ use rdp::parser::*;
+
+ static BYTES: [u8; 19] = [
+ 0x03, 0x00, 0x00, 0x13, 0x0e, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
+ ];
+ static TPKT_BEGIN: usize = 0;
+ static X224_BEGIN: usize = TPKT_BEGIN + 4;
+ static NEG_REQ_BEGIN: usize = X224_BEGIN + 7;
+ static NEG_REQ_END: usize = NEG_REQ_BEGIN + 8;
+ static X224_END: usize = NEG_REQ_END;
+ static TPKT_END: usize = X224_END;
+
+ #[test]
+ fn test_t123_incomplete() {
+ let t123_bytes = &BYTES[TPKT_BEGIN..TPKT_END - 1];
+ assert_eq!(
+ // fails: map_opt!(i2, be_u16, |x: u16| x.checked_sub(4))?
+ Err(nom::Err::Incomplete(nom::Needed::Size(
+ TPKT_END - TPKT_BEGIN - 4
+ ))),
+ parse_t123_tpkt(t123_bytes)
+ )
+ }
+
+ #[test]
+ fn test_x224_incomplete() {
+ let x224_bytes = &BYTES[X224_BEGIN..X224_END - 1];
+ assert_eq!(
+ // fails: expr_opt!(i5, length.checked_sub(6))?
+ // not counting a u8 length read, which was also successful
+ Err(nom::Err::Incomplete(nom::Needed::Size(
+ X224_END - X224_BEGIN - (6 + 1)
+ ))),
+ parse_x224_connection_request_class_0(x224_bytes)
+ )
+ }
+
+ #[test]
+ fn test_negotiate_incomplete() {
+ let neg_req_bytes = &BYTES[NEG_REQ_BEGIN..NEG_REQ_END - 1];
+ assert_eq!(
+ // fails: map_opt!(le_u32, num::FromPrimitive::from_u32)?
+ Err(nom::Err::Incomplete(nom::Needed::Size(
+ NEG_REQ_END - NEG_REQ_BEGIN - (1 + 1 + 2)
+ ))),
+ parse_negotiation_request(neg_req_bytes)
+ )
+ }
+}
+
+#[cfg(test)]
+mod tests_core_incomplete_49350 {
+ use nom;
+ use rdp::parser::*;
+
+ static BYTES: [u8; 428] = [
+ 0x03, 0x00, 0x01, 0xac, 0x02, 0xf0, 0x80, 0x7f, 0x65, 0x82, 0x01, 0xa0,
+ 0x04, 0x01, 0x01, 0x04, 0x01, 0x01, 0x01, 0x01, 0xff, 0x30, 0x19, 0x02,
+ 0x01, 0x22, 0x02, 0x01, 0x02, 0x02, 0x01, 0x00, 0x02, 0x01, 0x01, 0x02,
+ 0x01, 0x00, 0x02, 0x01, 0x01, 0x02, 0x02, 0xff, 0xff, 0x02, 0x01, 0x02,
+ 0x30, 0x19, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x02, 0x01, 0x01, 0x02,
+ 0x01, 0x01, 0x02, 0x01, 0x00, 0x02, 0x01, 0x01, 0x02, 0x02, 0x04, 0x20,
+ 0x02, 0x01, 0x02, 0x30, 0x1c, 0x02, 0x02, 0xff, 0xff, 0x02, 0x02, 0xfc,
+ 0x17, 0x02, 0x02, 0xff, 0xff, 0x02, 0x01, 0x01, 0x02, 0x01, 0x00, 0x02,
+ 0x01, 0x01, 0x02, 0x02, 0xff, 0xff, 0x02, 0x01, 0x02, 0x04, 0x82, 0x01,
+ 0x3f, 0x00, 0x05, 0x00, 0x14, 0x7c, 0x00, 0x01, 0x81, 0x36, 0x00, 0x08,
+ 0x00, 0x10, 0x00, 0x01, 0xc0, 0x00, 0x44, 0x75, 0x63, 0x61, 0x81, 0x28,
+ 0x01, 0xc0, 0xd8, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x05, 0x00, 0x03,
+ 0x01, 0xca, 0x03, 0xaa, 0x09, 0x04, 0x00, 0x00, 0x71, 0x17, 0x00, 0x00,
+ 0x53, 0x00, 0x45, 0x00, 0x52, 0x00, 0x56, 0x00, 0x45, 0x00, 0x52, 0x00,
+ 0x2d, 0x00, 0x58, 0x00, 0x59, 0x00, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0xca, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0f, 0x00,
+ 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0xc0, 0x0c, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x02, 0xc0, 0x0c, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x03, 0xc0, 0x38, 0x00, 0x04, 0x00, 0x00, 0x00, 0x72, 0x64, 0x70, 0x64,
+ 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x72, 0x64, 0x70, 0x73,
+ 0x6e, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x64, 0x72, 0x64, 0x79,
+ 0x6e, 0x76, 0x63, 0x00, 0x00, 0x00, 0x80, 0xc0, 0x63, 0x6c, 0x69, 0x70,
+ 0x72, 0x64, 0x72, 0x00, 0x00, 0x00, 0xa0, 0xc0,
+ ];
+ static X223_BEGIN: usize = 4;
+ static MCS_CONNECT_BEGIN: usize = X223_BEGIN + 3;
+ static MCS_CONNECT_END: usize = MCS_CONNECT_BEGIN + 421;
+ static _X223_END: usize = MCS_CONNECT_END;
+
+ #[test]
+ fn test_x223_incomplete() {
+ let x223_bytes = &BYTES[X223_BEGIN..X223_BEGIN + 2];
+ assert_eq!(
+ // fails: verify!(i2, be_u8, |x| x == 0x80)?
+ Err(nom::Err::Incomplete(nom::Needed::Size(1))),
+ parse_x223_data_class_0(x223_bytes)
+ )
+ }
+
+ #[test]
+ fn test_connect_incomplete() {
+ let connect_bytes = &BYTES[MCS_CONNECT_BEGIN..MCS_CONNECT_END - 1];
+ assert_eq!(
+ // fails: length_data!(i3, parse_per_length_determinant)?
+ // which reads the length (2) but not the full data (0x128)
+ Err(nom::Err::Incomplete(nom::Needed::Size(0x128))),
+ parse_mcs_connect(connect_bytes)
+ )
+ }
+}
--- /dev/null
+/* Copyright (C) 2019 Open Information Security Foundation
+ *
+ * You can copy, redistribute or modify this Program under the terms of
+ * the GNU General Public License version 2 as published by the Free
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+// Author: Zach Kelly <zach.kelly@lmco.com>
+
+//! RDP application layer
+
+use core::{
+ self, AppProto, DetectEngineState, Flow, ALPROTO_UNKNOWN, IPPROTO_TCP,
+};
+use nom;
+use parser::*;
+use rdp::parser::*;
+use std;
+use std::mem::transmute;
+use tls_parser::{
+ parse_tls_plaintext, TlsMessage, TlsMessageHandshake, TlsRecordType,
+};
+
+static mut ALPROTO_RDP: AppProto = ALPROTO_UNKNOWN;
+
+//
+// transactions
+//
+
+#[derive(Debug, PartialEq)]
+pub struct CertificateBlob {
+ pub data: Vec<u8>,
+}
+
+#[derive(Debug, PartialEq)]
+pub enum RdpTransactionItem {
+ X224ConnectionRequest(X224ConnectionRequest),
+ X224ConnectionConfirm(X224ConnectionConfirm),
+ McsConnectRequest(McsConnectRequest),
+ McsConnectResponse(McsConnectResponse),
+ TlsCertificateChain(Vec<CertificateBlob>),
+}
+
+#[derive(Debug, PartialEq)]
+pub struct RdpTransaction {
+ pub id: u64,
+ pub item: RdpTransactionItem,
+ // managed by macros `export_tx_get_detect_state!` and `export_tx_set_detect_state!`
+ de_state: Option<*mut DetectEngineState>,
+}
+
+impl RdpTransaction {
+ fn new(id: u64, item: RdpTransactionItem) -> Self {
+ Self {
+ id,
+ item,
+ de_state: None,
+ }
+ }
+
+ fn free(&mut self) {
+ if let Some(de_state) = self.de_state {
+ core::sc_detect_engine_state_free(de_state);
+ }
+ }
+}
+
+impl Drop for RdpTransaction {
+ fn drop(&mut self) {
+ self.free();
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn rs_rdp_state_get_tx(
+ state: *mut std::os::raw::c_void,
+ tx_id: u64,
+) -> *mut std::os::raw::c_void {
+ let state = cast_pointer!(state, RdpState);
+ match state.get_tx(tx_id) {
+ Some(tx) => {
+ return unsafe { transmute(tx) };
+ }
+ None => {
+ return std::ptr::null_mut();
+ }
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn rs_rdp_state_get_tx_count(
+ state: *mut std::os::raw::c_void,
+) -> u64 {
+ let state = cast_pointer!(state, RdpState);
+ return state.next_id;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_rdp_tx_get_progress_complete(
+ _direction: u8,
+) -> std::os::raw::c_int {
+ // a parser can implement a multi-step tx completion by using an arbitrary `n`
+ return 1;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_rdp_tx_get_progress(
+ _tx: *mut std::os::raw::c_void,
+ _direction: u8,
+) -> std::os::raw::c_int {
+ // tx complete when `rs_rdp_tx_get_progress(...) == rs_rdp_tx_get_progress_complete(...)`
+ // here, all transactions are immediately complete on insert
+ return 1;
+}
+
+//
+// state
+//
+
+#[derive(Debug, PartialEq)]
+pub struct RdpState {
+ next_id: u64,
+ to_client: Vec<u8>,
+ to_server: Vec<u8>,
+ transactions: Vec<RdpTransaction>,
+ tls_parsing: bool,
+ bypass_parsing: bool,
+}
+
+impl RdpState {
+ fn new() -> Self {
+ Self {
+ next_id: 0,
+ to_client: Vec::new(),
+ to_server: Vec::new(),
+ transactions: Vec::new(),
+ tls_parsing: false,
+ bypass_parsing: false,
+ }
+ }
+
+ fn free_tx(&mut self, tx_id: u64) {
+ let len = self.transactions.len();
+ let mut found = false;
+ let mut index = 0;
+ for ii in 0..len {
+ let tx = &self.transactions[ii];
+ if tx.id == tx_id {
+ found = true;
+ index = ii;
+ break;
+ }
+ }
+ if found {
+ self.transactions.remove(index);
+ }
+ }
+
+ fn get_tx(&self, tx_id: u64) -> Option<&RdpTransaction> {
+ for tx in &self.transactions {
+ if tx.id == tx_id {
+ return Some(tx);
+ }
+ }
+ return None;
+ }
+
+ fn new_tx(&mut self, item: RdpTransactionItem) -> RdpTransaction {
+ let tx = RdpTransaction::new(self.next_id, item);
+ self.next_id += 1;
+ return tx;
+ }
+
+ /// parse buffer captures from client to server
+ fn parse_ts(&mut self, input: &[u8]) -> bool {
+ // no need to process input buffer
+ if self.bypass_parsing {
+ return true;
+ }
+ // combine residual buffer with provided buffer
+ self.to_server.extend(input);
+ let temp: Vec<u8> = self.to_server.split_off(0);
+ let mut available = temp.as_slice();
+
+ loop {
+ if available.len() == 0 {
+ return true;
+ }
+ if self.tls_parsing {
+ match parse_tls_plaintext(&available) {
+ Ok((remainder, _tls)) => {
+ // update awaiting-parsing buffer
+ available = remainder;
+ }
+
+ Err(nom::Err::Incomplete(_)) => {
+ // save unparsed residual buffer for next parse
+ self.to_server.extend(available);
+ return true;
+ }
+
+ Err(nom::Err::Failure(_)) | Err(nom::Err::Error(_)) => {
+ return false;
+ }
+ }
+ } else {
+ // every message should be encapsulated within a T.123 tpkt
+ match parse_t123_tpkt(&available) {
+ // success
+ Ok((remainder, t123)) => {
+ // update awaiting-parsing buffer
+ available = remainder;
+ // evaluate message within the tpkt
+ match t123.child {
+ // X.224 connection request
+ T123TpktChild::X224ConnectionRequest(x224) => {
+ let tx = self.new_tx(
+ RdpTransactionItem::X224ConnectionRequest(
+ x224,
+ ),
+ );
+ self.transactions.push(tx);
+ }
+
+ // X.223 data packet, evaluate what it encapsulates
+ T123TpktChild::Data(x223) => {
+ match x223.child {
+ X223DataChild::McsConnectRequest(mcs) => {
+ let tx =
+ self.new_tx(RdpTransactionItem::McsConnectRequest(mcs));
+ self.transactions.push(tx);
+ }
+ // unknown message in X.223, skip
+ _ => (),
+ }
+ }
+
+ // unknown message in T.123, skip
+ _ => (),
+ }
+ }
+
+ Err(nom::Err::Incomplete(_)) => {
+ // save unparsed residual buffer for next parse
+ self.to_server.extend(available);
+ return true;
+ }
+
+ Err(nom::Err::Failure(_)) | Err(nom::Err::Error(_)) => {
+ if probe_tls_handshake(available) {
+ self.tls_parsing = true;
+ return self.parse_ts(available);
+ } else {
+ return false;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /// parse buffer captures from server to client
+ fn parse_tc(&mut self, input: &[u8]) -> bool {
+ // no need to process input buffer
+ if self.bypass_parsing {
+ return true;
+ }
+ // combine residual buffer with provided buffer
+ self.to_client.extend(input);
+ let temp: Vec<u8> = self.to_client.split_off(0);
+ let mut available = temp.as_slice();
+
+ loop {
+ if available.len() == 0 {
+ return true;
+ }
+ if self.tls_parsing {
+ match parse_tls_plaintext(&available) {
+ Ok((remainder, tls)) => {
+ // update awaiting-parsing buffer
+ available = remainder;
+ for message in &tls.msg {
+ match message {
+ TlsMessage::Handshake(
+ TlsMessageHandshake::Certificate(contents),
+ ) => {
+ let mut chain = Vec::new();
+ for cert in &contents.cert_chain {
+ chain.push(CertificateBlob {
+ data: cert.data.to_vec(),
+ });
+ }
+ let tx = self.new_tx(
+ RdpTransactionItem::TlsCertificateChain(
+ chain,
+ ),
+ );
+ self.transactions.push(tx);
+ self.bypass_parsing = true;
+ }
+ _ => {}
+ }
+ }
+ }
+
+ Err(nom::Err::Incomplete(_)) => {
+ // save unparsed residual buffer for next parse
+ self.to_client.extend(available);
+ return true;
+ }
+
+ Err(nom::Err::Failure(_)) | Err(nom::Err::Error(_)) => {
+ return false;
+ }
+ }
+ } else {
+ // every message should be encapsulated within a T.123 tpkt
+ match parse_t123_tpkt(&available) {
+ // success
+ Ok((remainder, t123)) => {
+ // update awaiting-parsing buffer
+ available = remainder;
+ // evaluate message within the tpkt
+ match t123.child {
+ // X.224 connection confirm
+ T123TpktChild::X224ConnectionConfirm(x224) => {
+ let tx = self.new_tx(
+ RdpTransactionItem::X224ConnectionConfirm(
+ x224,
+ ),
+ );
+ self.transactions.push(tx);
+ }
+
+ // X.223 data packet, evaluate what it encapsulates
+ T123TpktChild::Data(x223) => {
+ match x223.child {
+ X223DataChild::McsConnectResponse(mcs) => {
+ let tx = self
+ .new_tx(RdpTransactionItem::McsConnectResponse(mcs));
+ self.transactions.push(tx);
+ self.bypass_parsing = true;
+ return true;
+ }
+
+ // unknown message in X.223, skip
+ _ => (),
+ }
+ }
+
+ // unknown message in T.123, skip
+ _ => (),
+ }
+ }
+
+ Err(nom::Err::Incomplete(_)) => {
+ self.to_client.extend(available);
+ return true;
+ }
+
+ Err(nom::Err::Failure(_)) | Err(nom::Err::Error(_)) => {
+ if probe_tls_handshake(available) {
+ self.tls_parsing = true;
+ return self.parse_tc(available);
+ } else {
+ return false;
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn rs_rdp_state_new() -> *mut std::os::raw::c_void {
+ let state = RdpState::new();
+ let boxed = Box::new(state);
+ return unsafe { std::mem::transmute(boxed) };
+}
+
+#[no_mangle]
+pub extern "C" fn rs_rdp_state_free(state: *mut std::os::raw::c_void) {
+ let _drop: Box<RdpState> = unsafe { std::mem::transmute(state) };
+}
+
+#[no_mangle]
+pub extern "C" fn rs_rdp_state_tx_free(
+ state: *mut std::os::raw::c_void,
+ tx_id: u64,
+) {
+ let state = cast_pointer!(state, RdpState);
+ state.free_tx(tx_id);
+}
+
+//
+// detection state
+//
+
+export_tx_get_detect_state!(rs_rdp_tx_get_detect_state, RdpTransaction);
+export_tx_set_detect_state!(rs_rdp_tx_set_detect_state, RdpTransaction);
+
+//
+// probe
+//
+
+/// probe for T.123 type identifier, as each message is encapsulated in T.123
+fn probe_rdp(input: &[u8]) -> bool {
+ input.len() > 0 && input[0] == TpktVersion::T123 as u8
+}
+
+/// probe for T.123 message, whether to client or to server
+#[no_mangle]
+pub extern "C" fn rs_rdp_probe_ts_tc(
+ _flow: *const Flow,
+ _direction: u8,
+ input: *const u8,
+ input_len: u32,
+ _rdir: *mut u8,
+) -> AppProto {
+ if input != std::ptr::null_mut() {
+ // probe bytes for `rdp` protocol pattern
+ let slice = build_slice!(input, input_len as usize);
+
+ // Some sessions immediately (first byte) switch to TLS/SSL, e.g.
+ // https://wiki.wireshark.org/SampleCaptures?action=AttachFile&do=view&target=rdp-ssl.pcap.gz
+ // but this callback will not be exercised, so `probe_tls_handshake` not needed here.
+ if probe_rdp(slice) {
+ return unsafe { ALPROTO_RDP };
+ }
+ }
+ return ALPROTO_UNKNOWN;
+}
+
+/// probe for TLS
+fn probe_tls_handshake(input: &[u8]) -> bool {
+ input.len() > 0 && input[0] == u8::from(TlsRecordType::Handshake)
+}
+
+//
+// parse
+//
+
+#[no_mangle]
+pub extern "C" fn rs_rdp_parse_ts(
+ _flow: *const Flow,
+ state: *mut std::os::raw::c_void,
+ _pstate: *mut std::os::raw::c_void,
+ input: *const u8,
+ input_len: u32,
+ _data: *const std::os::raw::c_void,
+ _flags: u8,
+) -> i32 {
+ let state = cast_pointer!(state, RdpState);
+ let buf = build_slice!(input, input_len as usize);
+ // attempt to parse bytes as `rdp` protocol
+ if state.parse_ts(buf) {
+ return 1;
+ }
+ // no need for further parsing
+ return -1;
+}
+
+#[no_mangle]
+pub extern "C" fn rs_rdp_parse_tc(
+ _flow: *const Flow,
+ state: *mut std::os::raw::c_void,
+ _pstate: *mut std::os::raw::c_void,
+ input: *const u8,
+ input_len: u32,
+ _data: *const std::os::raw::c_void,
+ _flags: u8,
+) -> i32 {
+ let state = cast_pointer!(state, RdpState);
+ let buf = build_slice!(input, input_len as usize);
+ // attempt to parse bytes as `rdp` protocol
+ if state.parse_tc(buf) {
+ return 1;
+ }
+ // no need for further parsing
+ return -1;
+}
+
+//
+// registration
+//
+
+const PARSER_NAME: &'static [u8] = b"rdp\0";
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_rdp_register_parser() {
+ let default_port = std::ffi::CString::new("[3389]").unwrap();
+ let parser = RustParser {
+ name: PARSER_NAME.as_ptr() as *const std::os::raw::c_char,
+ default_port: default_port.as_ptr(),
+ ipproto: IPPROTO_TCP,
+ probe_ts: rs_rdp_probe_ts_tc,
+ probe_tc: rs_rdp_probe_ts_tc,
+ min_depth: 0,
+ max_depth: 16,
+ state_new: rs_rdp_state_new,
+ state_free: rs_rdp_state_free,
+ tx_free: rs_rdp_state_tx_free,
+ parse_ts: rs_rdp_parse_ts,
+ parse_tc: rs_rdp_parse_tc,
+ get_tx_count: rs_rdp_state_get_tx_count,
+ get_tx: rs_rdp_state_get_tx,
+ tx_get_comp_st: rs_rdp_tx_get_progress_complete,
+ tx_get_progress: rs_rdp_tx_get_progress,
+ get_tx_logged: None,
+ set_tx_logged: None,
+ get_de_state: rs_rdp_tx_get_detect_state,
+ set_de_state: rs_rdp_tx_set_detect_state,
+ get_events: None,
+ get_eventinfo: None,
+ get_eventinfo_byid: None,
+ localstorage_new: None,
+ localstorage_free: None,
+ get_tx_mpm_id: None,
+ set_tx_mpm_id: None,
+ get_files: None,
+ get_tx_iterator: None,
+ };
+
+ let ip_proto_str = std::ffi::CString::new("tcp").unwrap();
+
+ if AppLayerProtoDetectConfProtoDetectionEnabled(
+ ip_proto_str.as_ptr(),
+ parser.name,
+ ) != 0
+ {
+ let alproto = AppLayerRegisterProtocolDetection(&parser, 1);
+ ALPROTO_RDP = alproto;
+ if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name)
+ != 0
+ {
+ let _ = AppLayerRegisterParser(&parser, alproto);
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use rdp::parser::{RdpCookie, X224ConnectionRequest};
+
+ #[test]
+ fn test_probe_rdp() {
+ let buf: &[u8] = &[0x03, 0x00];
+ assert_eq!(true, probe_rdp(&buf));
+ }
+
+ #[test]
+ fn test_probe_rdp_other() {
+ let buf: &[u8] = &[0x04, 0x00];
+ assert_eq!(false, probe_rdp(&buf));
+ }
+
+ #[test]
+ fn test_probe_tls_handshake() {
+ let buf: &[u8] = &[0x16, 0x00];
+ assert_eq!(true, probe_tls_handshake(&buf));
+ }
+
+ #[test]
+ fn test_probe_tls_handshake_other() {
+ let buf: &[u8] = &[0x17, 0x00];
+ assert_eq!(false, probe_tls_handshake(&buf));
+ }
+
+ #[test]
+ fn test_parse_ts_rdp() {
+ let buf_1: &[u8] = &[0x03, 0x00, 0x00, 0x25, 0x20, 0xe0, 0x00, 0x00];
+ let buf_2: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x43, 0x6f, 0x6f, 0x6b, 0x69, 0x65, 0x3a, 0x20,
+ 0x6d, 0x73, 0x74, 0x73, 0x68, 0x61, 0x73, 0x68, 0x3d, 0x75, 0x73,
+ 0x65, 0x72, 0x31, 0x32, 0x33, 0x0d, 0x0a,
+ ];
+ let mut state = RdpState::new();
+ assert_eq!(true, state.parse_ts(&buf_1));
+ assert_eq!(0, state.transactions.len());
+ assert_eq!(true, state.parse_ts(&buf_2));
+ assert_eq!(1, state.transactions.len());
+ let item =
+ RdpTransactionItem::X224ConnectionRequest(X224ConnectionRequest {
+ cdt: 0,
+ dst_ref: 0,
+ src_ref: 0,
+ class: 0,
+ options: 0,
+ cookie: Some(RdpCookie {
+ mstshash: String::from("user123"),
+ }),
+ negotiation_request: None,
+ data: Vec::new(),
+ });
+ assert_eq!(item, state.transactions[0].item);
+ }
+
+ #[test]
+ fn test_parse_ts_other() {
+ let buf: &[u8] = &[0x03, 0x00, 0x00, 0x01, 0x00];
+ let mut state = RdpState::new();
+ assert_eq!(false, state.parse_ts(&buf));
+ }
+
+ #[test]
+ fn test_parse_tc_rdp() {
+ let buf_1: &[u8] = &[0x03, 0x00, 0x00, 0x09, 0x02];
+ let buf_2: &[u8] = &[0xf0, 0x80, 0x7f, 0x66];
+ let mut state = RdpState::new();
+ assert_eq!(true, state.parse_tc(&buf_1));
+ assert_eq!(0, state.transactions.len());
+ assert_eq!(true, state.parse_tc(&buf_2));
+ assert_eq!(1, state.transactions.len());
+ let item =
+ RdpTransactionItem::McsConnectResponse(McsConnectResponse {});
+ assert_eq!(item, state.transactions[0].item);
+ }
+
+ #[test]
+ fn test_parse_tc_other() {
+ let buf: &[u8] = &[0x03, 0x00, 0x00, 0x01, 0x00];
+ let mut state = RdpState::new();
+ assert_eq!(false, state.parse_tc(&buf));
+ }
+
+ #[test]
+ fn test_state_new_tx() {
+ let mut state = RdpState::new();
+ let item0 = RdpTransactionItem::McsConnectRequest(McsConnectRequest {
+ children: Vec::new(),
+ });
+ let item1 = RdpTransactionItem::McsConnectRequest(McsConnectRequest {
+ children: Vec::new(),
+ });
+ let tx0 = state.new_tx(item0);
+ let tx1 = state.new_tx(item1);
+ assert_eq!(2, state.next_id);
+ state.transactions.push(tx0);
+ state.transactions.push(tx1);
+ assert_eq!(2, state.transactions.len());
+ assert_eq!(0, state.transactions[0].id);
+ assert_eq!(1, state.transactions[1].id);
+ assert_eq!(false, state.tls_parsing);
+ assert_eq!(false, state.bypass_parsing);
+ }
+
+ #[test]
+ fn test_state_get_tx() {
+ let mut state = RdpState::new();
+ let item0 = RdpTransactionItem::McsConnectRequest(McsConnectRequest {
+ children: Vec::new(),
+ });
+ let item1 = RdpTransactionItem::McsConnectRequest(McsConnectRequest {
+ children: Vec::new(),
+ });
+ let item2 = RdpTransactionItem::McsConnectRequest(McsConnectRequest {
+ children: Vec::new(),
+ });
+ let tx0 = state.new_tx(item0);
+ let tx1 = state.new_tx(item1);
+ let tx2 = state.new_tx(item2);
+ state.transactions.push(tx0);
+ state.transactions.push(tx1);
+ state.transactions.push(tx2);
+ assert_eq!(Some(&state.transactions[1]), state.get_tx(1));
+ }
+
+ #[test]
+ fn test_state_free_tx() {
+ let mut state = RdpState::new();
+ let item0 = RdpTransactionItem::McsConnectRequest(McsConnectRequest {
+ children: Vec::new(),
+ });
+ let item1 = RdpTransactionItem::McsConnectRequest(McsConnectRequest {
+ children: Vec::new(),
+ });
+ let item2 = RdpTransactionItem::McsConnectRequest(McsConnectRequest {
+ children: Vec::new(),
+ });
+ let tx0 = state.new_tx(item0);
+ let tx1 = state.new_tx(item1);
+ let tx2 = state.new_tx(item2);
+ state.transactions.push(tx0);
+ state.transactions.push(tx1);
+ state.transactions.push(tx2);
+ state.free_tx(1);
+ assert_eq!(3, state.next_id);
+ assert_eq!(2, state.transactions.len());
+ assert_eq!(0, state.transactions[0].id);
+ assert_eq!(2, state.transactions[1].id);
+ assert_eq!(None, state.get_tx(1));
+ }
+}