diff --git a/markbase-core/src/vfs/smb_server_backend.rs b/markbase-core/src/vfs/smb_server_backend.rs index b7a2448..addea0d 100644 --- a/markbase-core/src/vfs/smb_server_backend.rs +++ b/markbase-core/src/vfs/smb_server_backend.rs @@ -285,18 +285,31 @@ impl Handle for VfsHandle { } } - async fn list_dir(&self, _pattern: Option<&str>) -> Result, SmbError> { + async fn list_dir(&self, pattern: Option<&str>) -> Result, SmbError> { match self { Self::File { .. } => Err(SmbError::NotADirectory), Self::Directory { vfs, path } => { let entries = vfs.read_dir(path).map_err(map_error)?; - let result = entries + let mut result: Vec = entries .into_iter() + .filter(|entry| { + let p = match pattern { + None => return true, + Some(p) => p, + }; + if p == "*" || p == "*.*" || p.is_empty() { + return true; + } + smb_match(&entry.name, p) + }) .map(|entry| { let info = vfs_stat_to_file_info(&entry.stat, &entry.name, path); DirEntry { info } }) .collect(); + for (i, entry) in result.iter_mut().enumerate() { + entry.info.file_index = (i + 1) as u64; + } Ok(result) } } @@ -307,6 +320,36 @@ impl Handle for VfsHandle { } } +/// Simple SMB wildcard match: `*` matches any sequence, `?` matches one char. +fn smb_match(name: &str, pattern: &str) -> bool { + let name = name.as_bytes(); + let pat = pattern.as_bytes(); + let mut ni = 0; + let mut pi = 0; + let mut star_idx: Option = None; + let mut match_idx = 0; + while ni < name.len() { + if pi < pat.len() && (pat[pi] == b'?' || pat[pi] == name[ni]) { + ni += 1; + pi += 1; + } else if pi < pat.len() && pat[pi] == b'*' { + star_idx = Some(pi); + match_idx = ni; + pi += 1; + } else if let Some(si) = star_idx { + pi = si + 1; + match_idx += 1; + ni = match_idx; + } else { + return false; + } + } + while pi < pat.len() && pat[pi] == b'*' { + pi += 1; + } + pi == pat.len() +} + fn filetime_to_systemtime(ft: u64) -> SystemTime { if ft < FILETIME_OFFSET { return SystemTime::UNIX_EPOCH; diff --git a/vendor/smb-server/src/dispatch.rs b/vendor/smb-server/src/dispatch.rs index 1c74b70..0df1352 100644 --- a/vendor/smb-server/src/dispatch.rs +++ b/vendor/smb-server/src/dispatch.rs @@ -581,6 +581,8 @@ async fn build_response_bytes( hdr.session_id = sid; } hdr.signature = [0u8; 16]; + // Grant at least 1 credit so clients (e.g. Samba smbclient) can proceed. + hdr.credit_request_response = hdr.credit_request_response.max(1); let request_was_signed = req_hdr.flags & SMB2_FLAGS_SIGNED != 0; // MS-SMB2 ยง3.3.5.5.3 step 12: SessionSetup SUCCESS must be signed for diff --git a/vendor/smb-server/src/fs/local.rs b/vendor/smb-server/src/fs/local.rs index 93ce9ca..4c543f9 100644 --- a/vendor/smb-server/src/fs/local.rs +++ b/vendor/smb-server/src/fs/local.rs @@ -694,7 +694,7 @@ impl Handle for LocalHandle { LocalHandle::Dir { dir_handle, .. } => { let dir_handle = Arc::clone(dir_handle); let pat = pattern.map(|s| s.to_owned()); - spawn_blocking(move || -> io::Result> { + let result: SmbResult> = spawn_blocking(move || -> io::Result> { let mut out = Vec::new(); for entry in dir_handle.entries()? { let entry = entry?; @@ -703,22 +703,26 @@ impl Handle for LocalHandle { continue; }; if let Some(p) = pat.as_deref() { - if !(p.is_empty() || p == "*" || p == "*.*" || glob_match(p, &name)) { + let matches_glob = p.is_empty() || p == "*" || p == "*.*" || glob_match(p, &name); + if !matches_glob { continue; } } let md = entry.metadata()?; let info = file_info_from_metadata(name, &md); - tracing::debug!(name = %info.name, is_dir = %info.is_directory, "list_dir entry"); out.push(SmbDirEntry { info }); } - tracing::debug!(count = %out.len(), "list_dir complete"); + // Assign unique file_index (1-based) so SMB2 FileId differs per entry + for (i, entry) in out.iter_mut().enumerate() { + entry.info.file_index = (i + 1) as u64; + } Ok(out) }) .await .map_err(join_to_io) .map_err(io_to_smb)? - .map_err(io_to_smb) + .map_err(io_to_smb); + return result; } } } diff --git a/vendor/smb-server/src/handlers/create.rs b/vendor/smb-server/src/handlers/create.rs index 6c2fe17..f48ef51 100644 --- a/vendor/smb-server/src/handlers/create.rs +++ b/vendor/smb-server/src/handlers/create.rs @@ -414,10 +414,13 @@ pub async fn handle( use crate::proto::messages::CreateContext; use crate::proto::messages::{ AaplCreateContextRequest, AaplCreateContextResponse, - SMB2_CRTCTX_AAPL_SERVER_QUERY, SMB2_CRTCTX_AAPL_SERVER_CAPS, - SMB2_CRTCTX_AAPL_VOLUME_CAPS, SMB2_CRTCTX_AAPL_MODEL_INFO, - SMB2_CRTCTX_AAPL_UNIX_BASED, SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR, + SMB2_CRTCTX_AAPL_SERVER_QUERY, + SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR, + SMB2_CRTCTX_AAPL_UNIX_BASED, + SMB2_CRTCTX_AAPL_SUPPORTS_NFS_ACE, SMB2_CRTCTX_AAPL_CASE_SENSITIVE, + SMB2_CRTCTX_AAPL_SUPPORT_RESOLVE_ID, + SMB2_CRTCTX_AAPL_FULL_SYNC, }; let contexts = CreateContext::parse_chain(&req.create_contexts).unwrap_or_default(); @@ -426,8 +429,12 @@ pub async fn handle( if let Some(ctx) = aapl_ctx { if let Some(aapl_req) = AaplCreateContextRequest::from_bytes(&ctx.data) { if aapl_req.command == SMB2_CRTCTX_AAPL_SERVER_QUERY { - let server_caps = SMB2_CRTCTX_AAPL_UNIX_BASED | SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR; - let volume_caps = SMB2_CRTCTX_AAPL_CASE_SENSITIVE; + let server_caps = SMB2_CRTCTX_AAPL_UNIX_BASED + | SMB2_CRTCTX_AAPL_SUPPORTS_READ_DIR_ATTR + | SMB2_CRTCTX_AAPL_SUPPORTS_NFS_ACE; + let volume_caps = SMB2_CRTCTX_AAPL_CASE_SENSITIVE + | SMB2_CRTCTX_AAPL_SUPPORT_RESOLVE_ID + | SMB2_CRTCTX_AAPL_FULL_SYNC; let aapl_resp = AaplCreateContextResponse::new_server_query( aapl_req.request_bitmap, aapl_req.client_caps, diff --git a/vendor/smb-server/src/handlers/query_directory.rs b/vendor/smb-server/src/handlers/query_directory.rs index 55a993a..c0e6952 100644 --- a/vendor/smb-server/src/handlers/query_directory.rs +++ b/vendor/smb-server/src/handlers/query_directory.rs @@ -13,6 +13,13 @@ use crate::ntstatus; use crate::server::ServerState; use crate::utils::utf16le_to_string; +fn hex_dump(label: &str, data: &[u8], max: usize) { + let show = data.len().min(max); + let hex: Vec = data[..show].iter().map(|b| format!("{:02x}", b)).collect(); + tracing::debug!("{}: len={} hex=[{}] {}", label, data.len(), hex.join(" "), + if data.len() > max { format!("... (truncated {})", data.len()) } else { String::new() }); +} + pub async fn handle( _server: &Arc, conn: &Arc, @@ -38,16 +45,24 @@ pub async fn handle( }; let pattern_str = utf16le_to_string(&req.file_name); - let pattern: Option = if pattern_str.is_empty() || pattern_str == "*" { + let is_empty_pat = pattern_str.is_empty(); + let pattern: Option = if is_empty_pat || pattern_str == "*" { None } else { - Some(pattern_str) + Some(pattern_str.clone()) }; let restart = req.flags & QueryDirectoryRequest::FLAG_RESTART_SCANS != 0 || req.flags & QueryDirectoryRequest::FLAG_REOPEN != 0; let single_entry = req.flags & QueryDirectoryRequest::FLAG_RETURN_SINGLE_ENTRY != 0; + tracing::debug!( + class = %req.file_information_class, output_buf_len = %req.output_buffer_length, + flags = %req.flags, restart = %restart, single = %single_entry, + pattern = %pattern_str, file_name_hex = ?req.file_name, + "QueryDirectory request" + ); + // Populate or refresh the cursor. { let mut open = open_arc.write().await; @@ -55,6 +70,8 @@ pub async fn handle( return HandlerResponse::err(ntstatus::STATUS_INVALID_PARAMETER); } if open.search_state.is_none() || restart { + let handle_type = open.handle.as_ref().map(|h| std::any::type_name_of_val(&*h)); + tracing::debug!("handle type: {:?}", handle_type); let entries = match open.handle.as_ref() { Some(h) => h.list_dir(pattern.as_deref()).await, None => return HandlerResponse::err(ntstatus::STATUS_FILE_CLOSED), @@ -63,6 +80,7 @@ pub async fn handle( Ok(e) => e, Err(e) => return HandlerResponse::err(e.to_nt_status()), }; + tracing::debug!(">>> list_dir count={}", entries.len()); open.search_state = Some(DirCursor { entries, next: 0, @@ -85,6 +103,7 @@ pub async fn handle( } let entry = &cursor.entries[cursor.next]; let file_index = entry.info.file_index; + tracing::debug!(name = %entry.info.name, file_index = %file_index, "encode_dir_entry file_index"); let mut bytes = encode_dir_entry(class_byte, entry, file_index); if bytes.is_empty() { cursor.next += 1; @@ -119,11 +138,14 @@ pub async fn handle( break; } } + // Log entries processed + tracing::debug!(next = cursor.next, total = cursor.entries.len(), buf_len = buf.len(), cap = cap, "QueryDirectory encoding done"); } if buf.is_empty() { return HandlerResponse::err(ntstatus::STATUS_NO_MORE_FILES); } - + hex_dump("QueryDirectory buffer", &buf, 512); + // Set last entry's NextEntryOffset to 0 (no next entry) if let Some(prev_off) = last_offset_pos { buf[prev_off..prev_off + 4].copy_from_slice(&0u32.to_le_bytes()); diff --git a/vendor/smb-server/src/info_class.rs b/vendor/smb-server/src/info_class.rs index 179e519..dbde46c 100644 --- a/vendor/smb-server/src/info_class.rs +++ b/vendor/smb-server/src/info_class.rs @@ -371,6 +371,7 @@ pub fn encode_dir_entry(class: u8, entry: &DirEntry, file_index: u64) -> Vec out.push(0); // ShortNameLength out.push(0); // Reserved1 out.extend_from_slice(&[0u8; 24]); // ShortName + out.extend_from_slice(&[0u8; 2]); // Reserved2 (alignment padding for FileId) out.extend_from_slice(&file_index.to_le_bytes()); // FileId (8 bytes) out.extend_from_slice(&name_u16); out