//! CREATE handler — open or create a file/directory and allocate a FileId. use std::sync::Arc; use crate::proto::header::Smb2Header; use crate::proto::messages::{CreateRequest, CreateResponse}; use tracing::{debug, warn}; use crate::backend::{OpenIntent, OpenOptions}; use crate::builder::Access; use crate::conn::state::{Connection, Open}; use crate::dispatch::HandlerResponse; use crate::handlers::shared::lookup_session_tree; use crate::ntstatus; use crate::path::SmbPath; use crate::server::ServerState; use crate::utils::utf16le_to_units; // MS-SMB2 §2.2.13 access mask flags const FILE_READ_DATA: u32 = 0x0000_0001; const FILE_WRITE_DATA: u32 = 0x0000_0002; const FILE_APPEND_DATA: u32 = 0x0000_0004; const FILE_READ_ATTRIBUTES: u32 = 0x0000_0080; const FILE_WRITE_ATTRIBUTES: u32 = 0x0000_0100; const DELETE: u32 = 0x0001_0000; const GENERIC_READ: u32 = 0x8000_0000; const GENERIC_WRITE: u32 = 0x4000_0000; const GENERIC_ALL: u32 = 0x1000_0000; const MAX_ALLOWED: u32 = 0x0200_0000; // CreateOptions const FILE_DIRECTORY_FILE: u32 = 0x0000_0001; const FILE_NON_DIRECTORY_FILE: u32 = 0x0000_0040; const FILE_DELETE_ON_CLOSE: u32 = 0x0000_1000; // CreateDisposition const FILE_SUPERSEDE: u32 = 0x0000_0000; const FILE_OPEN: u32 = 0x0000_0001; const FILE_CREATE: u32 = 0x0000_0002; const FILE_OPEN_IF: u32 = 0x0000_0003; const FILE_OVERWRITE: u32 = 0x0000_0004; const FILE_OVERWRITE_IF: u32 = 0x0000_0005; // CreateAction in response (MS-SMB2 §2.2.14) const FILE_OPENED: u32 = 0x0000_0001; const FILE_CREATED: u32 = 0x0000_0002; pub async fn handle( server: &Arc, conn: &Arc, hdr: &Smb2Header, body: &[u8], ) -> HandlerResponse { let req = match CreateRequest::parse(body) { Ok(r) => r, Err(_) => return HandlerResponse::err(ntstatus::STATUS_INVALID_PARAMETER), }; let tree_arc = match lookup_session_tree(conn, hdr).await { Ok(t) => t, Err(s) => return HandlerResponse::err(s), }; let tree = tree_arc.read().await; let granted = tree.granted_access; let backend = tree.share.backend.clone(); drop(tree); // Decode path. let units = match utf16le_to_units(&req.name) { Some(u) => u, None => return HandlerResponse::err(ntstatus::STATUS_OBJECT_NAME_INVALID), }; let path = match SmbPath::from_utf16(&units) { Ok(p) => p, Err(_) => return HandlerResponse::err(ntstatus::STATUS_OBJECT_NAME_INVALID), }; // Translate disposition. let intent = match req.create_disposition { FILE_SUPERSEDE | FILE_OVERWRITE_IF => OpenIntent::OverwriteOrCreate, FILE_OPEN => OpenIntent::Open, FILE_CREATE => OpenIntent::Create, FILE_OPEN_IF => OpenIntent::OpenOrCreate, FILE_OVERWRITE => OpenIntent::Truncate, _ => return HandlerResponse::err(ntstatus::STATUS_INVALID_PARAMETER), }; // Translate desired access into read/write hints. let want_read = req.desired_access & (FILE_READ_DATA | FILE_READ_ATTRIBUTES | GENERIC_READ | GENERIC_ALL | MAX_ALLOWED) != 0; let want_write = req.desired_access & (FILE_WRITE_DATA | FILE_APPEND_DATA | FILE_WRITE_ATTRIBUTES | DELETE | GENERIC_WRITE | GENERIC_ALL | MAX_ALLOWED) != 0; // Reject writes on a read-only tree. if want_write && !granted.allows_write() { warn!(path = %path, "write open on read-only tree"); return HandlerResponse::err(ntstatus::STATUS_ACCESS_DENIED); } // Disposition that creates: requires write permission. if !granted.allows_write() && matches!( intent, OpenIntent::Create | OpenIntent::OpenOrCreate | OpenIntent::OverwriteOrCreate | OpenIntent::Truncate ) { return HandlerResponse::err(ntstatus::STATUS_ACCESS_DENIED); } let directory = req.create_options & FILE_DIRECTORY_FILE != 0; let non_directory = req.create_options & FILE_NON_DIRECTORY_FILE != 0; if directory && non_directory { return HandlerResponse::err(ntstatus::STATUS_INVALID_PARAMETER); } let delete_on_close = req.create_options & FILE_DELETE_ON_CLOSE != 0; let opts = OpenOptions { read: want_read || !want_write, write: want_write, intent, directory, non_directory, delete_on_close, }; let handle = match backend.open(&path, opts).await { Ok(h) => h, Err(e) => { debug!(error = %e, path = %path, "backend open failed"); return HandlerResponse::err(e.to_nt_status()); } }; // Stat for the response. let info = match handle.stat().await { Ok(i) => i, Err(e) => { let _ = handle.close().await; return HandlerResponse::err(e.to_nt_status()); } }; // Allocate FileId, register Open. let tree = tree_arc.write().await; let file_id = tree.alloc_file_id(); // Phase 4: Oplock support - use OplockManager to determine granted level let requested_oplock = req.requested_oplock_level; let granted_oplock = if requested_oplock == 0 { 0 // No oplock requested } else { // Check with OplockManager server.oplock_manager.can_grant( &path, requested_oplock, req.share_access, if want_write { granted } else { Access::Read }, ).await.unwrap_or(0) }; // Register with OplockManager if oplock granted if granted_oplock > 0 { use crate::oplock::OplockEntry; server.oplock_manager.register(&path, OplockEntry { file_id, tree_id: tree.id, session_id: hdr.session_id, oplock_level: granted_oplock, share_access: req.share_access, granted_access: if want_write { granted } else { Access::Read }, connection_id: 0, // Will be tracked in Phase 3 }).await; } // Phase 3: Check for lease request in create contexts (SMB 3.x) let (lease_key, lease_state) = if !req.create_contexts.is_empty() { use crate::proto::messages::CreateContext; let contexts = CreateContext::parse_chain(&req.create_contexts).unwrap_or_default(); // Find RqLs (REQUEST_LEASE) context let lease_ctx = contexts.iter().find(|ctx| ctx.name == CreateContext::NAME_RQLS); if let Some(ctx) = lease_ctx { // Parse lease request (MS-SMB2 §2.2.13.2) // Data format: LeaseKey (16 bytes) + LeaseState (4 bytes) + LeaseFlags (4 bytes) if ctx.data.len() >= 24 { let lease_key_bytes: [u8; 16] = ctx.data[0..16].try_into().unwrap_or([0; 16]); let lease_state = u32::from_le_bytes([ctx.data[16], ctx.data[17], ctx.data[18], ctx.data[19]]); // Check if lease can be granted if server.lease_manager.can_grant(lease_state).await { // Register lease use crate::oplock::LeaseEntry; server.lease_manager.register(LeaseEntry { lease_key: lease_key_bytes, lease_state, lease_flags: 0, file_id, path: path.clone(), session_id: hdr.session_id, tree_id: tree.id, }).await; (Some(lease_key_bytes), Some(lease_state)) } else { (None, None) } } else { (None, None) } } else { (None, None) } } else { (None, None) }; let open = Open::new( file_id, handle, if want_write { granted } else { Access::Read }, path.clone(), info.is_directory, delete_on_close, granted_oplock, // oplock_level req.share_access, // share_access ); // Phase 3: Set lease fields if granted let open_arc = Arc::new(tokio::sync::RwLock::new(open)); if lease_key.is_some() { let mut open_mut = open_arc.write().await; open_mut.lease_key = lease_key; open_mut.lease_state = lease_state; open_mut.lease_flags = Some(0); } tree.opens.write().await.insert(file_id, open_arc.clone()); drop(tree); let create_action = match intent { OpenIntent::Create => FILE_CREATED, OpenIntent::OpenOrCreate | OpenIntent::OverwriteOrCreate => FILE_OPENED, OpenIntent::Open | OpenIntent::Truncate => FILE_OPENED, }; let resp = CreateResponse { structure_size: 89, oplock_level: granted_oplock, // Phase 4: will be dynamic flags: 0, create_action, creation_time: info.creation_time, last_access_time: info.last_access_time, last_write_time: info.last_write_time, change_time: info.change_time, allocation_size: info.allocation_size, end_of_file: info.end_of_file, file_attributes: info.attributes(), 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"); HandlerResponse::ok(buf) }