diff --git a/vendor/smb-server/src/proto/messages/dfs.rs b/vendor/smb-server/src/proto/messages/dfs.rs new file mode 100644 index 0000000..1d7f627 --- /dev/null +++ b/vendor/smb-server/src/proto/messages/dfs.rs @@ -0,0 +1,198 @@ +//! DFS Referral Support (MS-DFSR) +//! Implements DFS_Get_Referrals and DFS_Get_Referrals_Ex IOCTL handlers + +use crate::proto::error::{ProtoError, ProtoResult}; + +/// DFS Referral Request (MS-DFSR §2.2.2) +/// Simplified structure for parsing +pub struct DfsReferralRequest { + pub max_referral_level: u16, + pub requested_file_name: String, +} + +impl DfsReferralRequest { + pub fn parse(buf: &[u8]) -> ProtoResult { + if buf.len() < 2 { + return Err(ProtoError::Malformed("DFS referral request too short")); + } + + let max_referral_level = u16::from_le_bytes([buf[0], buf[1]]); + let requested_file_name = String::from_utf8_lossy(&buf[2..]) + .trim_end_matches('\0') + .to_string(); + + Ok(Self { + max_referral_level, + requested_file_name, + }) + } +} + +/// DFS Referral Response (MS-DFSR §2.2.3) +/// Version 2 referral (most common) +pub struct DfsReferralResponse { + pub version: u16, + pub referral_count: u16, + pub referral_header_flags: u32, + /// Referral entries (offsets + strings) + pub referrals_data: Vec, +} + +impl DfsReferralResponse { + /// Build version 2 referral response + pub fn build_v2(dfs_path: &str, network_address: &str) -> Vec { + let mut out = Vec::new(); + + // Response header (16 bytes) + out.extend_from_slice(&2u16.to_le_bytes()); // Version + out.extend_from_slice(&1u16.to_le_bytes()); // ReferralCount + out.extend_from_slice(&0u32.to_le_bytes()); // ReferralHeaderFlags + + // Referral entry header (16 bytes) + out.extend_from_slice(&2u16.to_le_bytes()); // Version + out.extend_from_slice(&0u16.to_le_bytes()); // ReferralFlags + out.extend_from_slice(&16u32.to_le_bytes()); // DfsPathOffset + out.extend_from_slice(&16u32.to_le_bytes()); // AltDfsPathOffset + out.extend_from_slice(&16u32.to_le_bytes()); // NetworkAddressOffset + + // DFS path (UTF-8 with null terminator) + out.extend_from_slice(dfs_path.as_bytes()); + out.push(0); // null terminator + + // Alternate DFS path (same as DFS path) + out.extend_from_slice(dfs_path.as_bytes()); + out.push(0); // null terminator + + // Network address (UTF-8 with null terminator) + out.extend_from_slice(network_address.as_bytes()); + out.push(0); // null terminator + + out + } +} + +/// DFS Namespace Configuration +#[derive(Debug, Clone)] +pub struct DfsNamespace { + /// DFS root path (e.g., "\\server\dfs") + pub root_path: String, + /// DFS targets (list of server addresses) + pub targets: Vec, +} + +#[derive(Debug, Clone)] +pub struct DfsTarget { + /// Target server address (e.g., "192.168.1.100") + pub address: String, + /// Target share name (e.g., "share1") + pub share_name: String, +} + +impl DfsNamespace { + pub fn new(root_path: String) -> Self { + Self { + root_path, + targets: Vec::new(), + } + } + + pub fn add_target(&mut self, address: String, share_name: String) { + self.targets.push(DfsTarget { address, share_name }); + } + + /// Build referral response for a DFS path + pub fn build_referral_response(&self, requested_path: &str) -> Option> { + // Check if requested path is under DFS root + if !requested_path.starts_with(&self.root_path) { + return None; + } + + // Use first target for referral + if self.targets.is_empty() { + return None; + } + + let target = &self.targets[0]; + let network_address = format!("\\{}\\{}", target.address, target.share_name); + + Some(DfsReferralResponse::build_v2(&self.root_path, &network_address)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_dfs_namespace_creation() { + let namespace = DfsNamespace::new("\\server\\dfs".to_string()); + assert_eq!(namespace.root_path, "\\server\\dfs"); + assert_eq!(namespace.targets.len(), 0); + } + + #[test] + fn test_add_dfs_target() { + let mut namespace = DfsNamespace::new("\\server\\dfs".to_string()); + namespace.add_target("192.168.1.100".to_string(), "share1".to_string()); + namespace.add_target("192.168.1.101".to_string(), "share2".to_string()); + + assert_eq!(namespace.targets.len(), 2); + assert_eq!(namespace.targets[0].address, "192.168.1.100"); + assert_eq!(namespace.targets[0].share_name, "share1"); + } + + #[test] + fn test_build_referral_response() { + let mut namespace = DfsNamespace::new("\\server\\dfs".to_string()); + namespace.add_target("192.168.1.100".to_string(), "share1".to_string()); + + let response = namespace.build_referral_response("\\server\\dfs\\path"); + assert!(response.is_some()); + + let response = response.unwrap(); + assert!(response.len() > 16); + + // Check version field + let version = u16::from_le_bytes([response[0], response[1]]); + assert_eq!(version, 2); + } + + #[test] + fn test_referral_response_version_2() { + let data = DfsReferralResponse::build_v2( + "\\server\\dfs", + "\\192.168.1.100\\share1" + ); + + // Header: Version (2) + ReferralCount (1) + Flags (0) = 8 bytes + // Entry header: Version (2) + Flags (0) + 3 offsets = 16 bytes + // Total header: 24 bytes + + assert!(data.len() > 24); + + let version = u16::from_le_bytes([data[0], data[1]]); + assert_eq!(version, 2); + + let count = u16::from_le_bytes([data[2], data[3]]); + assert_eq!(count, 1); + } + + #[test] + fn test_dfs_path_mismatch() { + let mut namespace = DfsNamespace::new("\\server\\dfs".to_string()); + namespace.add_target("192.168.1.100".to_string(), "share1".to_string()); + + // Requested path not under DFS root + let response = namespace.build_referral_response("\\other\\path"); + assert!(response.is_none()); + } + + #[test] + fn test_empty_targets() { + let namespace = DfsNamespace::new("\\server\\dfs".to_string()); + + // No targets configured + let response = namespace.build_referral_response("\\server\\dfs"); + assert!(response.is_none()); + } +} \ No newline at end of file diff --git a/vendor/smb-server/src/proto/messages/mod.rs b/vendor/smb-server/src/proto/messages/mod.rs index c9d6231..e82be64 100644 --- a/vendor/smb-server/src/proto/messages/mod.rs +++ b/vendor/smb-server/src/proto/messages/mod.rs @@ -11,6 +11,7 @@ pub mod cancel; pub mod change_notify; pub mod close; pub mod create; +pub mod dfs; pub mod echo; pub mod error_response; pub mod flush;