Files
markbase/vendor/smb-server/src/handlers/tree_connect.rs

189 lines
5.9 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! TREE_CONNECT handler — share lookup + authorization.
use std::sync::Arc;
use crate::proto::auth::ntlm::Identity;
use crate::proto::header::Smb2Header;
use crate::proto::messages::{TreeConnectRequest, TreeConnectResponse};
use tracing::{info, warn};
use crate::builder::Access;
use crate::conn::state::{Connection, TreeConnect};
use crate::dispatch::HandlerResponse;
use crate::handlers::shared::lookup_session;
use crate::ntstatus;
use crate::server::{ServerState, ShareMode};
const SHARE_TYPE_DISK: u8 = 0x01;
const SHARE_TYPE_PIPE: u8 = 0x02;
const FILE_GENERIC_READ: u32 = 0x0012_0089;
const FILE_GENERIC_EXECUTE: u32 = 0x0012_00A0;
const FILE_ALL_ACCESS: u32 = 0x001F_01FF;
const SMB2_SHAREFLAG_MANUAL_CACHING: u32 = 0x00000000;
const SMB2_SHAREFLAG_ACCESS_BASED_DIRECTORY_ENUM: u32 = 0x00080000;
const SMB2_SHARE_CAP_DFS: u32 = 0x00000001;
pub async fn handle(
server: &Arc<ServerState>,
conn: &Arc<Connection>,
hdr: &Smb2Header,
body: &[u8],
) -> HandlerResponse {
let req = match TreeConnectRequest::parse(body) {
Ok(r) => r,
Err(_) => return HandlerResponse::err(ntstatus::STATUS_INVALID_PARAMETER),
};
let path = req.path_str().unwrap_or_default();
tracing::debug!(%path, "tree connect path");
let share_name = match extract_share_name(&path) {
Some(s) => s,
None => {
tracing::warn!(%path, "tree connect: empty share name");
return HandlerResponse::err(ntstatus::STATUS_BAD_NETWORK_NAME);
}
};
tracing::debug!(%share_name, "tree connect lookup");
let sess_arc = match lookup_session(conn, hdr.session_id).await {
Ok(s) => s,
Err(s) => return HandlerResponse::err(s),
};
let sess = sess_arc.read().await;
let identity = sess.identity.clone();
drop(sess);
// IPC$: synthetic share. Accept at TREE_CONNECT (Windows always probes
// it before mounting an actual share); downstream CREATE/IOCTL on it
// return NotSupported via the no-op backend.
let share = if share_name.eq_ignore_ascii_case("IPC$") {
crate::server::ShareBindings::ipc()
} else {
match server.find_share(&share_name).await {
Some(s) => s,
None => return HandlerResponse::err(ntstatus::STATUS_BAD_NETWORK_NAME),
}
};
// Authorize.
let acl = share.acl.read().await;
let granted = match authorize(&acl.mode, &acl.users, &identity) {
Some(a) => a,
None => {
warn!(?identity, share = %share.name, "TREE_CONNECT denied");
return HandlerResponse::err(ntstatus::STATUS_ACCESS_DENIED);
}
};
drop(acl);
// Backend cap.
let granted = if share.backend.capabilities().is_read_only {
granted.clamp_to(Access::Read)
} else {
granted
};
let tree_id = sess_arc.read().await.alloc_tree_id();
let tc = Arc::new(tokio::sync::RwLock::new(TreeConnect::new(
tree_id,
share.clone(),
granted,
)));
{
let sess = sess_arc.read().await;
let mut trees = sess.trees.write().await;
trees.insert(tree_id, tc);
}
let maximal_access = match granted {
Access::Read => FILE_GENERIC_READ | FILE_GENERIC_EXECUTE,
Access::ReadWrite => FILE_ALL_ACCESS,
};
// Time Machine: set xattr on share root directory
if share.time_machine {
use crate::path::SmbPath;
let root_path = SmbPath::root();
// Generate UUID for this Time Machine backup
let uuid = uuid::Uuid::new_v4();
let uuid_bytes = uuid.as_bytes();
// Set com.apple.TimeMachine.SupportedFilesStoreUUID
share.backend.set_xattr(&root_path, "com.apple.TimeMachine.SupportedFilesStoreUUID", uuid_bytes).await.ok();
// Set com.apple.TimeMachine.SupportsThisDevice (1 = true)
share.backend.set_xattr(&root_path, "com.apple.TimeMachine.SupportsThisDevice", &[1]).await.ok();
// Set max_size if specified
if let Some(max_size) = share.time_machine_max_size {
let max_size_bytes = max_size.to_le_bytes();
share.backend.set_xattr(&root_path, "com.apple.TimeMachine.MaxSize", &max_size_bytes).await.ok();
}
tracing::info!(share = %share.name, uuid = %uuid, "Time Machine enabled");
}
let share_flags = if share.is_ipc {
0
} else {
SMB2_SHAREFLAG_MANUAL_CACHING | SMB2_SHAREFLAG_ACCESS_BASED_DIRECTORY_ENUM
};
let capabilities = if share.is_ipc {
0
} else {
0 // 普通磁盘 share不是 DFS share
};
let capabilities = if share.is_ipc {
0
} else {
SMB2_SHARE_CAP_DFS // Basic DFS support for Finder
};
let resp = TreeConnectResponse {
structure_size: 16,
share_type: if share.is_ipc {
SHARE_TYPE_PIPE
} else {
SHARE_TYPE_DISK
},
reserved: 0,
share_flags,
capabilities,
maximal_access,
};
let mut buf = Vec::new();
resp.write_to(&mut buf).expect("encode");
info!(tree_id, share = %share.name, ?granted, "tree connect");
let mut hr = HandlerResponse::ok(buf);
hr.override_tree_id = Some(tree_id);
hr
}
fn extract_share_name(unc: &str) -> Option<String> {
// \\server\share or \\server\share\
let trimmed = unc.trim_end_matches(['\\', '/']);
let parts: Vec<&str> = trimmed
.split(['\\', '/'])
.filter(|s| !s.is_empty())
.collect();
parts.last().map(|s| s.to_string())
}
fn authorize(
mode: &ShareMode,
users: &std::collections::HashMap<String, Access>,
identity: &Identity,
) -> Option<Access> {
match mode {
ShareMode::Public => Some(Access::ReadWrite),
ShareMode::PublicReadOnly => Some(Access::Read),
ShareMode::AuthenticatedOnly => match identity {
Identity::Anonymous => None,
Identity::User { user, .. } => users.get(user).copied(),
},
}
}