Skip to main content

smb_server_proto_smb1/
create.rs

1//! `SMB_COM_NT_CREATE_ANDX` ([MS-SMB] §2.2.4.9).
2
3use smb_server_proto::buf::Reader;
4
5use crate::session_setup::BodyError;
6
7impl From<smb_server_proto::error::ProtoError> for BodyError {
8    fn from(_: smb_server_proto::error::ProtoError) -> Self {
9        BodyError::TooShort
10    }
11}
12
13/// Parsed client request ([MS-SMB] §2.2.4.9.1, WC=24).
14#[derive(Debug)]
15pub struct NtCreateReq {
16    /// Name length in bytes (includes the NUL for Unicode names).
17    pub name_len: usize,
18    /// FID of a directory to open relative paths against (0 = share root).
19    pub root_fid: u32,
20    /// Desired access mask (generic + specific bits).
21    pub desired_access: u32,
22    /// Extended file attributes for newly created objects.
23    pub ext_attrs: u32,
24    /// Create disposition (`SUPERSEDE`..`OVERWRITE_IF`).
25    pub disposition: u32,
26    /// Create options (DIRECTORY_FILE, DELETE_ON_CLOSE, …).
27    pub create_options: u32,
28    /// Object name (decoded; empty = open the share root itself).
29    pub name: String,
30}
31
32fn u16le(w: &[u8], off: usize) -> u16 {
33    w.get(off..off + 2)
34        .map(|s| u16::from_le_bytes(s.try_into().unwrap()))
35        .unwrap_or(0)
36}
37
38fn u32le(w: &[u8], off: usize) -> u32 {
39    w.get(off..off + 4)
40        .map(|s| u32::from_le_bytes(s.try_into().unwrap()))
41        .unwrap_or(0)
42}
43
44impl NtCreateReq {
45    /// Parse the WC=24 request. `data_base` is the absolute frame offset of
46    /// the data area so Unicode parity is honoured when the client pads
47    /// before the name.
48    pub fn parse(
49        words: &[u8],
50        data: &[u8],
51        unicode: bool,
52        bc_off_abs: usize,
53    ) -> Result<Self, BodyError> {
54        use super::session_setup::BodyError;
55        if words.len() < 48 || data.is_empty() {
56            return Err(BodyError::TooShort);
57        }
58        let name_len = u16le(words, 5) as usize;
59        let root_fid = u32le(words, 11);
60        let desired_access = u32le(words, 15);
61        let ext_attrs = u32le(words, 27);
62        let disposition = u32le(words, 35);
63        let create_options = u32le(words, 39);
64
65        // Skip one pad byte when the data area starts at an odd offset.
66        let mut start = 0;
67        if !bc_off_abs.is_multiple_of(2) && !data.is_empty() {
68            start = 1;
69        }
70        let mut rd = Reader::new(data, start);
71        let raw_len = name_len.min(data.len().saturating_sub(start));
72        let raw = rd.take(raw_len)?.to_vec();
73        let name = decode_name(&raw, unicode);
74
75        Ok(NtCreateReq {
76            name_len,
77            root_fid,
78            desired_access,
79            ext_attrs,
80            disposition,
81            create_options,
82            name,
83        })
84    }
85}
86
87/// Decode a create-name blob honouring the Unicode flag.
88fn decode_name(raw: &[u8], unicode: bool) -> String {
89    if unicode {
90        let units: Vec<u16> = raw
91            .chunks_exact(2)
92            .map(|c| c[0] as u16 | ((c[1] as u16) << 8))
93            .take_while(|&u| u != 0)
94            .collect();
95        String::from_utf16_lossy(&units)
96    } else {
97        String::from_utf8_lossy(raw.split(|&b| b == 0).next().unwrap_or(&[])).into_owned()
98    }
99}
100
101/// Build the WC=34 success response ([MS-SMB] §2.2.4.9.2): oplock level,
102/// FID, create action, four FILETIMEs, attributes, allocation/EOF sizes.
103#[allow(clippy::too_many_arguments)]
104pub fn build_response(
105    fid: u16,
106    action: u32,
107    times: [smb_server_proto::types::FileTime; 4],
108    attrs: u32,
109    alloc: u64,
110    eof: u64,
111    is_dir: bool,
112) -> Vec<u8> {
113    let mut params = Vec::with_capacity(68);
114    params.push(0xFF); // terminal AndX
115    params.push(0);
116    params.extend_from_slice(&0u16.to_le_bytes());
117    params.push(0); // OplockLevel: none granted
118    params.extend_from_slice(&fid.to_le_bytes());
119    params.extend_from_slice(&action.to_le_bytes());
120    for t in &times {
121        params.extend_from_slice(&t.0.to_le_bytes());
122    }
123    params.extend_from_slice(&attrs.to_le_bytes());
124    params.extend_from_slice(&alloc.to_le_bytes());
125    params.extend_from_slice(&eof.to_le_bytes());
126    params.extend_from_slice(&0u16.to_le_bytes()); // FileType: disk
127    params.extend_from_slice(&0u16.to_le_bytes()); // DeviceState
128    params.push(if is_dir { 1 } else { 0 }); // Directory flag
129    params
130}
131
132/// Map an I/O error kind onto an NT status for create paths.
133pub fn map_create_err(e: &std::io::Error) -> smb_server_proto::types::Status {
134    use smb_server_proto::types::Status;
135    match e.kind() {
136        std::io::ErrorKind::NotFound => Status::OBJECT_NAME_NOT_FOUND,
137        std::io::ErrorKind::PermissionDenied => Status::ACCESS_DENIED,
138        std::io::ErrorKind::AlreadyExists => Status::OBJECT_NAME_COLLISION,
139        _ => Status::UNSUCCESSFUL,
140    }
141}