diff --git a/data/auth.sqlite b/data/auth.sqlite index a6d0b98..0c58056 100644 Binary files a/data/auth.sqlite and b/data/auth.sqlite differ diff --git a/vendor/smb-server/src/backend.rs b/vendor/smb-server/src/backend.rs index 2c360c5..97848d8 100644 --- a/vendor/smb-server/src/backend.rs +++ b/vendor/smb-server/src/backend.rs @@ -409,3 +409,200 @@ impl Handle for AfpInfoHandle { self.save_to_xattr().await } } + +// --------------------------------------------------------------------------- +// AFP_Resource Handle (AppleDouble file ._filename) +// --------------------------------------------------------------------------- + +/// AppleDouble file format (._filename) +/// Reference: https://developer.apple.com/library/archive/documentation/macpdf/Inside_Macintosh/Inside_Macintosh.pdf +pub struct AfpResourceHandle { + base_path: SmbPath, + backend: std::sync::Arc, + resource_path: SmbPath, + data: tokio::sync::RwLock>>, + read_only: bool, +} + +impl AfpResourceHandle { + pub fn new(base_path: SmbPath, backend: std::sync::Arc, read_only: bool) -> Self { + // AppleDouble file: ._filename in same directory + let file_name = base_path.file_name().unwrap_or(""); + let parent = base_path.parent(); + let resource_name = format!("._{}", file_name); + + let resource_path = match parent { + Some(p) => p.join(&resource_name).unwrap_or_else(|_| base_path.clone()), + None => SmbPath::root(), + }; + + Self { + base_path, + backend, + resource_path, + data: tokio::sync::RwLock::new(None), + read_only, + } + } + + pub async fn load_from_file(&self) -> SmbResult<()> { + // Try to open ._filename file + let opts = OpenOptions { + read: true, + write: false, + intent: OpenIntent::Open, + directory: false, + non_directory: true, + delete_on_close: false, + }; + + match self.backend.open(&self.resource_path, opts).await { + Ok(handle) => { + let info = handle.stat().await?; + let size = info.end_of_file as usize; + let data = handle.read(0, size as u32).await?; + let mut buffer = self.data.write().await; + *buffer = Some(data.to_vec()); + Ok(()) + } + Err(SmbError::NotFound | SmbError::PathNotFound) => { + // ._file doesn't exist, initialize empty buffer + let mut buffer = self.data.write().await; + *buffer = Some(vec![]); + Ok(()) + } + Err(e) => Err(e), + } + } + + pub async fn save_to_file(&self) -> SmbResult<()> { + if self.read_only { + return Err(SmbError::AccessDenied); + } + + let buffer = self.data.read().await; + if let Some(data) = buffer.as_ref() { + if data.is_empty() { + // Don't create empty ._file + return Ok(()) + } + + let opts = OpenOptions { + read: false, + write: true, + intent: OpenIntent::OverwriteOrCreate, + directory: false, + non_directory: true, + delete_on_close: false, + }; + + let handle = self.backend.open(&self.resource_path, opts).await?; + handle.write(0, data).await?; + handle.flush().await?; + let _ = handle.close().await; + } + Ok(()) + } +} + +#[async_trait] +impl Handle for AfpResourceHandle { + async fn read(&self, offset: u64, len: u32) -> SmbResult { + self.load_from_file().await?; + let buffer = self.data.read().await; + + match buffer.as_ref() { + Some(data) => { + let start = offset as usize; + let end = std::cmp::min(start + len as usize, data.len()); + if start >= data.len() { + return Ok(bytes::Bytes::new()); + } + Ok(bytes::Bytes::copy_from_slice(&data[start..end])) + } + None => Ok(bytes::Bytes::new()), + } + } + + async fn write(&self, offset: u64, data: &[u8]) -> SmbResult { + if self.read_only { + return Err(SmbError::AccessDenied); + } + + let mut buffer = self.data.write().await; + match buffer.as_mut() { + Some(buf) => { + let start = offset as usize; + // Extend buffer if needed + if start + data.len() > buf.len() { + buf.resize(start + data.len(), 0); + } + buf[start..start + data.len()].copy_from_slice(data); + Ok(data.len() as u32) + } + None => { + *buffer = Some(vec![0u8; offset as usize + data.len()]); + if let Some(buf) = buffer.as_mut() { + buf[offset as usize..].copy_from_slice(data); + } + Ok(data.len() as u32) + } + } + } + + async fn flush(&self) -> SmbResult<()> { + self.save_to_file().await + } + + async fn stat(&self) -> SmbResult { + self.load_from_file().await?; + let buffer = self.data.read().await; + + let size = match buffer.as_ref() { + Some(data) => data.len() as u64, + None => 0, + }; + + Ok(FileInfo { + name: format!("._{}", self.base_path.file_name().unwrap_or("")), + end_of_file: size, + allocation_size: size, + creation_time: 0, + last_access_time: 0, + last_write_time: 0, + change_time: 0, + is_directory: false, + file_index: 0, + }) + } + + async fn set_times(&self, _times: FileTimes) -> SmbResult<()> { + Err(SmbError::NotSupported) + } + + async fn truncate(&self, len: u64) -> SmbResult<()> { + if self.read_only { + return Err(SmbError::AccessDenied); + } + + let mut buffer = self.data.write().await; + match buffer.as_mut() { + Some(buf) => { + buf.resize(len as usize, 0); + Ok(()) + } + None => { + *buffer = Some(vec![0u8; len as usize]); + Ok(()) + } + } + } + + async fn list_dir(&self, _pattern: Option<&str>) -> SmbResult> { + Err(SmbError::NotSupported) + } + + async fn close(self: Box) -> SmbResult<()> { + self.save_to_file().await + } +} diff --git a/vendor/smb-server/src/handlers/create.rs b/vendor/smb-server/src/handlers/create.rs index 3f334f4..6c2fe17 100644 --- a/vendor/smb-server/src/handlers/create.rs +++ b/vendor/smb-server/src/handlers/create.rs @@ -7,7 +7,7 @@ use crate::proto::header::Smb2Header; use crate::proto::messages::{CreateRequest, CreateResponse}; use tracing::{debug, warn}; -use crate::backend::{OpenIntent, OpenOptions}; +use crate::backend::{OpenIntent, OpenOptions, FileInfo}; use crate::builder::Access; use crate::conn::state::{Connection, Open}; use crate::dispatch::HandlerResponse; @@ -144,11 +144,86 @@ pub async fn handle( if stream_path.is_afp_resource() { debug!(base_path = %stream_path.base_path(), stream = %stream_path.stream_name(), "AFP_Resource named stream open"); - // For AFP_Resource, we return a virtual handle that reads/writes ._ file - // TODO: Implement actual AFP_Resource handling via AppleDouble files + // Create AfpResourceHandle to read/write AppleDouble file (._filename) + let base_smb_path = stream_path.base_path().clone(); - // Return STATUS_OBJECT_NAME_NOT_FOUND for now (phase 3 will implement) - return HandlerResponse::err(ntstatus::STATUS_OBJECT_NAME_NOT_FOUND); + // Check write permission + let want_write = req.desired_access & (FILE_WRITE_DATA | GENERIC_WRITE | GENERIC_ALL) != 0; + if want_write && !granted.allows_write() { + return HandlerResponse::err(ntstatus::STATUS_ACCESS_DENIED); + } + + // Allocate file_id + let mut tree = tree_arc.write().await; + let file_id = tree.alloc_file_id(); + let read_only = tree.share.backend.capabilities().is_read_only; + + let handle = crate::backend::AfpResourceHandle::new(base_smb_path.clone(), backend, want_write && read_only); + + let open = Open::new( + file_id, + Box::new(handle), + if want_write { granted } else { Access::Read }, + base_smb_path, + false, // is_directory + false, // delete_on_close + 0, // oplock_level + 0, // share_access + ); + + let open_arc = Arc::new(RwLock::new(open)); + tree.opens.write().await.insert(file_id, open_arc.clone()); + drop(tree); + + // Load resource fork size for response + let open_lock = open_arc.read().await; + let info = match open_lock.handle.as_ref() { + Some(h) => h.stat().await.unwrap_or(FileInfo { + name: "".to_string(), + end_of_file: 0, + allocation_size: 0, + creation_time: 0, + last_access_time: 0, + last_write_time: 0, + change_time: 0, + is_directory: false, + file_index: 0, + }), + None => FileInfo { + name: "".to_string(), + end_of_file: 0, + allocation_size: 0, + creation_time: 0, + last_access_time: 0, + last_write_time: 0, + change_time: 0, + is_directory: false, + file_index: 0, + }, + }; + drop(open_lock); + + let resp = CreateResponse { + structure_size: 89, + oplock_level: 0, + flags: 0, + create_action: FILE_OPENED, + creation_time: 0, + last_access_time: 0, + last_write_time: 0, + change_time: 0, + allocation_size: info.allocation_size, + end_of_file: info.end_of_file, + file_attributes: 0, + reserved2: 0, + file_id, + create_contexts_offset: 0, + create_contexts_length: 0, + create_contexts: vec![], + }; + let mut buf = Vec::new(); + resp.write_to(&mut buf).expect("encode"); + return HandlerResponse::ok(buf); } // Unknown named stream type