Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions smb-core/src/nt_status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ pub enum NTStatus {
UserSessionDeleted = 0xC0000203,
NetworkSessionExpired = 0xC000035C,
FileNotAvailable = 0xC0000467,
FileClosed = 0xC0000128,
EndOfFile = 0xC0000011,
InvalidInfoClass = 0xC0000003,
InvalidDeviceRequest = 0xC0000010,
BufferOverflow = 0x80000005,
InfoLengthMismatch = 0xC0000004,
UnknownError = 0xFFFFFFFF,
}

Expand Down
2 changes: 1 addition & 1 deletion smb/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ async fn main() -> SMBResult<()> {
.unencrypted_access(true)
.require_message_signing(false)
.encrypt_data(false)
.add_fs_share("test".into(), "".into(), file_allowed, get_file_perms)
.add_fs_share("test".into(), std::env::var("SMB_SHARE_PATH").unwrap_or_default(), file_allowed, get_file_perms)
.add_ipc_share()
.auth_provider(NTLMAuthProvider::new(vec![
User::new("tejasmehta", "password"),
Expand Down
124 changes: 123 additions & 1 deletion smb/src/protocol/body/close/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::protocol::body::create::file_attributes::SMBFileAttributes;
use crate::protocol::body::create::file_id::SMBFileId;
use crate::protocol::body::filetime::FileTime;

mod flags;
pub mod flags;

#[derive(
Debug,
Expand All @@ -32,6 +32,16 @@ pub struct SMBCloseRequest {
file_id: SMBFileId,
}

impl SMBCloseRequest {
pub fn flags(&self) -> SMBCloseFlags {
self.flags
}

pub fn file_id(&self) -> &SMBFileId {
&self.file_id
}
}

#[derive(
Debug,
PartialEq,
Expand Down Expand Up @@ -63,4 +73,116 @@ pub struct SMBCloseResponse {
end_of_file: u64,
#[smb_direct(start(fixed = 56))]
file_attributes: SMBFileAttributes,
}

impl SMBCloseResponse {
pub fn from_metadata(metadata: &crate::server::share::SMBFileMetadata, attributes: SMBFileAttributes) -> Self {
Self {
flags: SMBCloseFlags::POSTQUERY_ATTRIB,
reserved: PhantomData,
creation_time: metadata.creation_time().clone(),
last_access_time: metadata.last_access_time().clone(),
last_write_time: metadata.last_write_time().clone(),
change_time: metadata.last_modification_time().clone(),
allocation_size: metadata.allocated_size(),
end_of_file: metadata.actual_size(),
file_attributes: attributes,
}
}

pub fn empty() -> Self {
Self {
flags: SMBCloseFlags::empty(),
reserved: PhantomData,
creation_time: FileTime::zero(),
last_access_time: FileTime::zero(),
last_write_time: FileTime::zero(),
change_time: FileTime::zero(),
allocation_size: 0,
end_of_file: 0,
file_attributes: SMBFileAttributes::empty(),
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use smb_core::{SMBByteSize, SMBToBytes, SMBFromBytes};

#[test]
fn close_response_empty_has_zero_fields() {
let resp = SMBCloseResponse::empty();
assert_eq!(resp.flags, SMBCloseFlags::empty());
assert_eq!(resp.allocation_size, 0);
assert_eq!(resp.end_of_file, 0);
assert_eq!(resp.file_attributes, SMBFileAttributes::empty());
}

#[test]
fn close_response_empty_serialization_round_trip() {
let resp = SMBCloseResponse::empty();
let bytes = resp.smb_to_bytes();
assert_eq!(bytes.len(), resp.smb_byte_size());
let (_, parsed) = SMBCloseResponse::smb_from_bytes(&bytes).unwrap();
assert_eq!(resp, parsed);
}

#[test]
fn close_response_from_metadata_sets_postquery_flag() {
use crate::server::share::SMBFileMetadata;
let metadata = SMBFileMetadata::new(
FileTime::from_unix(1700000000),
FileTime::from_unix(1700000100),
FileTime::from_unix(1700000200),
FileTime::from_unix(1700000300),
4096,
1024,
);
let resp = SMBCloseResponse::from_metadata(&metadata, SMBFileAttributes::NORMAL);
assert!(resp.flags.contains(SMBCloseFlags::POSTQUERY_ATTRIB));
assert_eq!(resp.allocation_size, 4096);
assert_eq!(resp.end_of_file, 1024);
assert_eq!(resp.file_attributes, SMBFileAttributes::NORMAL);
}

#[test]
fn close_response_from_metadata_serialization_round_trip() {
use crate::server::share::SMBFileMetadata;
let metadata = SMBFileMetadata::new(
FileTime::from_unix(1700000000),
FileTime::from_unix(1700000100),
FileTime::from_unix(1700000200),
FileTime::from_unix(1700000300),
8192,
2048,
);
let resp = SMBCloseResponse::from_metadata(&metadata, SMBFileAttributes::ARCHIVE);
let bytes = resp.smb_to_bytes();
assert_eq!(bytes.len(), resp.smb_byte_size());
let (_, parsed) = SMBCloseResponse::smb_from_bytes(&bytes).unwrap();
assert_eq!(resp, parsed);
}

#[test]
fn close_request_accessors() {
let file_id = SMBFileId::new(42, 99);
let bytes = {
let mut buf = Vec::new();
// struct_size (u16) = 24
buf.extend_from_slice(&24u16.to_le_bytes());
// flags (u16) = POSTQUERY_ATTRIB = 0x0001
buf.extend_from_slice(&1u16.to_le_bytes());
// reserved (4 bytes)
buf.extend_from_slice(&[0u8; 4]);
// file_id: persistent (u64) + volatile (u64)
buf.extend_from_slice(&42u64.to_le_bytes());
buf.extend_from_slice(&99u64.to_le_bytes());
buf
};
let (_, req) = SMBCloseRequest::smb_from_bytes(&bytes).unwrap();
assert_eq!(req.file_id().persistent(), file_id.persistent());
assert_eq!(req.file_id().volatile(), file_id.volatile());
assert!(req.flags().contains(SMBCloseFlags::POSTQUERY_ATTRIB));
}
}
71 changes: 68 additions & 3 deletions smb/src/protocol/body/create/file_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,72 @@ use smb_derive::{SMBByteSize, SMBFromBytes, SMBToBytes};
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize, Clone, SMBFromBytes, SMBByteSize, SMBToBytes)]
pub struct SMBFileId {
#[smb_direct(start(fixed = 0))]
pub persistent: u64,
persistent: u64,
#[smb_direct(start(fixed = 8))]
pub volatile: u64,
}
volatile: u64,
}

impl SMBFileId {
pub fn new(persistent: u64, volatile: u64) -> Self {
Self { persistent, volatile }
}

pub fn persistent(&self) -> u64 {
self.persistent
}

pub fn volatile(&self) -> u64 {
self.volatile
}
}

#[cfg(test)]
mod tests {
use super::*;
use smb_core::{SMBByteSize, SMBFromBytes, SMBToBytes};

/// MS-SMB2 §2.2.14.1: SMB2_FILEID is 16 bytes (Persistent u64 + Volatile u64)
#[test]
fn file_id_is_16_bytes() {
let fid = SMBFileId::new(0, 0);
assert_eq!(fid.smb_byte_size(), 16);
}

/// Persistent is at offset 0, Volatile at offset 8
#[test]
fn file_id_wire_layout() {
let fid = SMBFileId::new(0xDEAD, 0xBEEF);
let bytes = fid.smb_to_bytes();
assert_eq!(bytes.len(), 16);
let persistent = u64::from_le_bytes(bytes[0..8].try_into().unwrap());
let volatile = u64::from_le_bytes(bytes[8..16].try_into().unwrap());
assert_eq!(persistent, 0xDEAD);
assert_eq!(volatile, 0xBEEF);
}

#[test]
fn file_id_round_trip() {
let fid = SMBFileId::new(42, 99);
let bytes = fid.smb_to_bytes();
let (_, parsed) = SMBFileId::smb_from_bytes(&bytes).unwrap();
assert_eq!(fid, parsed);
}

/// Per §2.2.14.1, persistent and volatile are distinct fields.
/// Verify they serialize independently.
#[test]
fn file_id_persistent_and_volatile_are_independent() {
let a = SMBFileId::new(1, 2);
let b = SMBFileId::new(2, 1);
let bytes_a = a.smb_to_bytes();
let bytes_b = b.smb_to_bytes();
assert_ne!(bytes_a, bytes_b);
}

#[test]
fn file_id_getters() {
let fid = SMBFileId::new(100, 200);
assert_eq!(fid.persistent(), 100);
assert_eq!(fid.volatile(), 200);
}
}
14 changes: 7 additions & 7 deletions smb/src/protocol/body/create/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ pub struct SMBCreateRequest {
create_disposition: SMBCreateDisposition,
#[smb_direct(start(fixed = 40))]
create_options: SMBCreateOptions,
#[smb_string(order = 0, start(inner(start = 44, num_type = "u16", subtract = 68)), length(inner(start = 46, num_type = "u16")), underlying = "u16")]
#[smb_string(order = 0, start(inner(start = 44, num_type = "u16", subtract = 64)), length(inner(start = 46, num_type = "u16")), underlying = "u16")]
file_name: String,
#[smb_vector(order = 1, align = 8, length(inner(start = 52, num_type = "u32")), offset(inner(start = 48, num_type = "u32", subtract = 64)))]
contexts: Vec<CreateRequestContext>,
Expand Down Expand Up @@ -169,12 +169,12 @@ impl SMBCreateResponse {
oplock_level: open.oplock_level(),
flags: SMBCreateFlags::empty(),
action: SMBCreateAction::Created,
creation_time: metadata.creation_time,
last_access_time: metadata.last_access_time,
last_write_time: metadata.last_write_time,
change_time: metadata.last_modification_time,
allocation_size: metadata.allocated_size,
end_of_file: metadata.actual_size,
creation_time: metadata.creation_time().clone(),
last_access_time: metadata.last_access_time().clone(),
last_write_time: metadata.last_write_time().clone(),
change_time: metadata.last_modification_time().clone(),
allocation_size: metadata.allocated_size(),
end_of_file: metadata.actual_size(),
attributes: open.file_attributes(),
reserved: PhantomData,
file_id: open.file_id(),
Expand Down
55 changes: 55 additions & 0 deletions smb/src/protocol/body/file_info/access.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use bitflags::bitflags;
use serde::{Deserialize, Serialize};

use smb_derive::{SMBByteSize, SMBFromBytes, SMBToBytes};

use crate::util::flags_helper::{impl_smb_byte_size_for_bitflag, impl_smb_from_bytes_for_bitflag, impl_smb_to_bytes_for_bitflag};

/// ACCESS_MASK flags for FILE_ACCESS_INFORMATION (MS-FSCC 2.4.1).
///
/// These are the same ACCESS_MASK values defined in [MS-DTYP] §2.4.3 /
/// [MS-SMB2] §2.2.13.1, representing the access rights granted on the open.
bitflags! {
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
pub struct FileAccessFlags: u32 {
const FILE_READ_DATA = 0x00000001;
const FILE_WRITE_DATA = 0x00000002;
const FILE_APPEND_DATA = 0x00000004;
const FILE_READ_EA = 0x00000008;
const FILE_WRITE_EA = 0x00000010;
const FILE_EXECUTE = 0x00000020;
const FILE_DELETE_CHILD = 0x00000040;
const FILE_READ_ATTRIBUTES = 0x00000080;
const FILE_WRITE_ATTRIBUTES = 0x00000100;
const DELETE = 0x00010000;
const READ_CONTROL = 0x00020000;
const WRITE_DAC = 0x00040000;
const WRITE_OWNER = 0x00080000;
const SYNCHRONIZE = 0x00100000;
const ACCESS_SYSTEM_SECURITY = 0x01000000;
const MAXIMUM_ALLOWED = 0x02000000;
const GENERIC_ALL = 0x10000000;
const GENERIC_EXECUTE = 0x20000000;
const GENERIC_WRITE = 0x40000000;
const GENERIC_READ = 0x80000000;
}
}

impl_smb_byte_size_for_bitflag! { FileAccessFlags }
impl_smb_to_bytes_for_bitflag! { FileAccessFlags }
impl_smb_from_bytes_for_bitflag! { FileAccessFlags }

/// FILE_ACCESS_INFORMATION (MS-FSCC 2.4.1) — 4 bytes
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, SMBByteSize, SMBFromBytes, SMBToBytes)]
pub struct FileAccessInformation {
#[smb_direct(start(fixed = 0))]
access_flags: FileAccessFlags,
}

impl FileAccessInformation {
pub fn new(access_flags: FileAccessFlags) -> Self {
Self { access_flags }
}

pub fn access_flags(&self) -> FileAccessFlags { self.access_flags }
}
64 changes: 64 additions & 0 deletions smb/src/protocol/body/file_info/alignment.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
use num_enum::TryFromPrimitive;
use serde::{Deserialize, Serialize};

use smb_core::{SMBByteSize, SMBFromBytes, SMBParseResult, SMBToBytes};
use smb_core::error::SMBError;
use smb_derive::{SMBByteSize as SMBByteSizeDerive, SMBFromBytes as SMBFromBytesDerive, SMBToBytes as SMBToBytesDerive};

/// Device alignment requirements (MS-FSCC 2.4.3).
///
/// Each value specifies the address boundary the device requires
/// for data transfers. For example, `Quad` means the device requires
/// 8-byte aligned addresses.
#[repr(u32)]
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, TryFromPrimitive)]
pub enum FileAlignmentRequirement {
Byte = 0x00000000,
Word = 0x00000001,
Long = 0x00000003,
Quad = 0x00000007,
Octa = 0x0000000F,
Align32 = 0x0000001F,
Align64 = 0x0000003F,
Align128 = 0x0000007F,
Align256 = 0x000000FF,
Align512 = 0x000001FF,
}

impl SMBByteSize for FileAlignmentRequirement {
fn smb_byte_size(&self) -> usize {
std::mem::size_of::<u32>()
}
}

impl SMBFromBytes for FileAlignmentRequirement {
fn smb_from_bytes(input: &[u8]) -> SMBParseResult<&[u8], Self> where Self: Sized {
u32::smb_from_bytes(input)
.map(|(remaining, val)| {
let req = Self::try_from_primitive(val)
.map_err(SMBError::parse_error)?;
Ok((remaining, req))
})?
}
}

impl SMBToBytes for FileAlignmentRequirement {
fn smb_to_bytes(&self) -> Vec<u8> {
(*self as u32).smb_to_bytes()
}
}

/// FILE_ALIGNMENT_INFORMATION (MS-FSCC 2.4.3) — 4 bytes
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize, SMBByteSizeDerive, SMBFromBytesDerive, SMBToBytesDerive)]
pub struct FileAlignmentInformation {
#[smb_direct(start(fixed = 0))]
alignment_requirement: FileAlignmentRequirement,
}

impl FileAlignmentInformation {
pub fn new(alignment_requirement: FileAlignmentRequirement) -> Self {
Self { alignment_requirement }
}

pub fn alignment_requirement(&self) -> FileAlignmentRequirement { self.alignment_requirement }
}
Loading
Loading