Files
markbase/vendor/smb-server/src/handlers/create.rs
Warren 344d13435e
Some checks failed
Test / build (push) Has been cancelled
Test / test (push) Has been cancelled
Implement SMB 3.x Lease support Phase 3
- CREATE handler parse RqLs create context
- Extract LeaseKey (16 bytes) + LeaseState (4 bytes)
- Check can_grant() before registration
- Register with LeaseManager
- Set Open.lease_key/lease_state fields

All 229 tests pass.
2026-06-21 01:23:32 +08:00

278 lines
9.3 KiB
Rust

//! 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<ServerState>,
conn: &Arc<Connection>,
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)
}