Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
39944dc
feat: implement Close, Read, and QueryInfo handlers for E2E file read
tejas-claude-bot[bot] Feb 8, 2026
f2fe296
fix: resolve file read integration test failures
tejas-claude-bot[bot] Feb 10, 2026
09ea922
refactor: replace raw Vec<u8> info buffers with typed MS-FSCC structs
tejas-claude-bot[bot] Feb 10, 2026
56eb98e
refactor: split file_info structs into separate files per project con…
tejas-claude-bot[bot] Feb 12, 2026
27765c8
fix: correct FileId construction, close cleanup, and DurableFileId ve…
tejas-claude-bot[bot] Mar 29, 2026
f04b7b4
refactor: replace manual FileAllInformation::to_bytes() with SMBToByt…
tejas-claude-bot[bot] Mar 29, 2026
a806452
refactor: replace raw u32 fields with typed bitflags/enums in file_info
tejas-claude-bot[bot] Mar 31, 2026
73e40d1
fix: address security bugs and encapsulate struct fields
tejas-claude-bot[bot] Apr 4, 2026
2fa3140
fix: resolve clippy warnings from rebase
tejas-claude-bot[bot] Apr 5, 2026
7ccb0b1
fix: create response after open registration, handle short reads, and…
tejas-claude-bot[bot] Apr 7, 2026
d19dd86
fix(clippy): gate SMBByteSize import and suppress unused variable war…
tejas-claude-bot[bot] Apr 7, 2026
96adf53
feat: implement Write handler for E2E file write
tejas-claude-bot[bot] Apr 5, 2026
d85e689
test: add E2E smbclient tests for Write handler
tejas-claude-bot[bot] Apr 5, 2026
f72b568
test: remove #[ignore] from smbclient integration tests
tejas-claude-bot[bot] Apr 5, 2026
8e7cbea
fix: use write_all to prevent short writes and add write_data tests
tejas-claude-bot[bot] Apr 7, 2026
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
7 changes: 6 additions & 1 deletion smb/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,12 @@ 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![
Expand Down
127 changes: 126 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, PartialEq, Eq, SMBByteSize, SMBToBytes, SMBFromBytes, Serialize, Deserialize, Clone,
Expand All @@ -24,6 +24,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, Eq, SMBByteSize, SMBToBytes, SMBFromBytes, Serialize, Deserialize, Clone,
)]
Expand All @@ -48,3 +58,118 @@ pub struct SMBCloseResponse {
#[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, SMBFromBytes, SMBToBytes};

#[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));
}
}
72 changes: 70 additions & 2 deletions smb/src/protocol/body/create/file_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,75 @@ use smb_derive::{SMBByteSize, SMBFromBytes, 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 section 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 section 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 @@ -65,7 +65,7 @@ pub struct SMBCreateRequest {
create_options: SMBCreateOptions,
#[smb_string(
order = 0,
start(inner(start = 44, num_type = "u16", subtract = 68)),
start(inner(start = 44, num_type = "u16", subtract = 64)),
length(inner(start = 46, num_type = "u16")),
underlying = "u16"
)]
Expand Down Expand Up @@ -179,12 +179,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
61 changes: 61 additions & 0 deletions smb/src/protocol/body/file_info/access.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
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,
};

bitflags! {
/// 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.
#[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
}
}
Loading
Loading