Skip to main content

smb_server_proto_smb1/
trans2.rs

1//! TRANSACTION2 envelope ([MS-CIFS] §2.2.4.46) and the subcommand extensions
2//! defined by [MS-SMB] §2.2.6: FIND_FIRST2 (§2.2.6.1), FIND_NEXT2 (§2.2.6.2),
3//! QUERY_FS (§2.2.6.3), QUERY/SET PATH (§2.2.6.5/6), QUERY/SET FILE
4//! (§2.2.6.7/8). Information levels per §2.2.8.
5
6use smb_server_proto::buf::Reader;
7
8/// TRANS2 subcommand codes ([MS-SMB] §2.2.6).
9pub mod subcmd {
10    /// TRANS2_FIND_FIRST2.
11    pub const FIND_FIRST2: u8 = 0x01;
12    /// TRANS2_FIND_NEXT2.
13    pub const FIND_NEXT2: u8 = 0x02;
14    /// TRANS2_QUERY_FS_INFORMATION.
15    pub const QUERY_FS_INFO: u8 = 0x03;
16    /// TRANS2_SET_FS_INFORMATION.
17    pub const SET_FS_INFO: u8 = 0x04;
18    /// TRANS2_QUERY_PATH_INFORMATION.
19    pub const QUERY_PATH_INFO: u8 = 0x05;
20    /// TRANS2_SET_PATH_INFORMATION.
21    pub const SET_PATH_INFO: u8 = 0x06;
22    /// TRANS2_QUERY_FILE_INFORMATION.
23    pub const QUERY_FILE_INFO: u8 = 0x07;
24    /// TRANS2_SET_FILE_INFORMATION.
25    pub const SET_FILE_INFO: u8 = 0x08;
26}
27
28/// Information levels for QUERY/SET operations ([MS-SMB] §2.2.8).
29pub mod level {
30    /// Basic information (times + attributes).
31    pub const BASIC: u16 = 0x004;
32    /// Standard information (sizes, links, dir flag).
33    pub const STANDARD: u16 = 0x005;
34    /// Extended attribute size.
35    pub const EA_SIZE: u16 = 0x006;
36    /// Stream information.
37    pub const STREAMS: u16 = 0x010;
38    /// Pass-through `FILE_BASIC_INFORMATION`.
39    pub const PT_BASIC: u16 = 0x1004;
40    /// Pass-through `FILE_STANDARD_INFORMATION`.
41    pub const PT_STANDARD: u16 = 0x1005;
42    /// Pass-through `FILE_INTERNAL_INFORMATION`.
43    pub const PT_INTERNAL: u16 = 0x1006;
44    /// Pass-through `FILE_EA_INFORMATION`.
45    pub const PT_EA: u16 = 0x1007;
46    /// Pass-through name query.
47    pub const PT_NAME: u16 = 0x1009;
48    /// Pass-through allocation size.
49    pub const PT_ALLOC: u16 = 0x100B;
50    /// Pass-through end-of-file.
51    pub const PT_EOF: u16 = 0x100C;
52    /// Pass-through disposition.
53    pub const PT_DISPOSITION: u16 = 0x100D;
54    /// Pass-through all-information composite.
55    pub const PT_ALL: u16 = 0x1012;
56    /// Pass-through alternate name.
57    pub const PT_ALT_NAME: u16 = 0x1015;
58    /// Client getattr composite (SMB_QUERY_FILE_ATTRIBUTE).
59    pub const ATTRIBUTE_COMPOSITE: u16 = 0x107;
60}
61
62/// Find information levels ([MS-SMB] §2.2.8.1).
63pub mod find_level {
64    /// DOS-standard OEM-name entries.
65    pub const STANDARD: u16 = 0x001;
66    /// Unicode names only.
67    pub const DIRECTORY: u16 = 0x102;
68    /// Full directory info.
69    pub const FULL: u16 = 0x103;
70    /// Both directory info (with short name slot).
71    pub const BOTH: u16 = 0x104;
72    /// Names only.
73    pub const NAMES: u16 = 0x105;
74}
75
76/// Parsed TRANSACTION2 request envelope: subcommand plus fixed parameters
77/// and data sections.
78#[derive(Debug)]
79pub struct Trans2Req {
80    /// Subcommand byte from the start of the parameter buffer, or Setup[0].
81    pub subcmd: u8,
82    /// Fixed parameter words following the subcommand/pad.
83    pub params: Vec<u8>,
84    /// Data section contents (clamped to the frame).
85    pub data: Vec<u8>,
86    /// Absolute frame offset of `params[0]` (parity reference).
87    pub param_base: usize,
88    /// Absolute frame offset of `data[0]`.
89    pub data_base: usize,
90    /// Unicode strings requested by the client.
91    pub unicode: bool,
92}
93
94impl Trans2Req {
95    /// Parse the envelope from the request frame body.
96    ///
97    /// Two client styles are supported:
98    /// * SetupCount=0 — subcommand is the first parameter-buffer byte,
99    ///   followed by one pad byte and the fixed words.
100    /// * SetupCount≥1 — the subcommand rides in `Setup[0]`; the buffer holds
101    ///   only the fixed fields (Impacket style).
102    pub fn parse(
103        _wct: u8,
104        words: &[u8],
105        buf: &[u8],
106        _bc_off_abs: usize,
107        unicode: bool,
108    ) -> Option<Trans2Req> {
109        if words.len() < 30 {
110            return None;
111        }
112        let g = |i: usize| -> u16 {
113            if i + 2 <= words.len() {
114                u16::from_le_bytes([words[i], words[i + 1]])
115            } else {
116                0
117            }
118        };
119        let pcount = g(18) as usize; // vwv9 ParameterCount
120        let poff = g(20) as usize; // vwv10 ParameterOffset
121        let dcount = g(24) as usize; // vwv12 DataCount
122        let doff = g(26) as usize; // vwv13 DataOffset
123
124        if poff > buf.len() || poff + pcount > buf.len() {
125            return None;
126        }
127        let params_all = &buf[poff..poff + pcount];
128        if params_all.is_empty() {
129            return None;
130        }
131        let setup_count = *words.get(26)? as usize;
132        let (subcmd, fixed, param_base) = if setup_count >= 1 {
133            // Impacket style: Setup[0] carries the subcommand.
134            let sc = *words.get(28).unwrap_or(&params_all[0]);
135            (sc, params_all.to_vec(), poff)
136        } else if pcount >= 2 {
137            // Classic style: subcommand byte then pad then fixed words.
138            (params_all[0], params_all[2..].to_vec(), poff + 2)
139        } else {
140            return None;
141        };
142
143        // Clamp bogus data sections instead of rejecting: request data is
144        // only consumed by SET_FILE/PATH handlers which re-check bounds.
145        let mut dcount = dcount;
146        if doff > buf.len() || doff + dcount > buf.len() {
147            dcount = buf.len().saturating_sub(doff).min(dcount);
148        }
149        let data: Vec<u8> = if dcount > 0 && doff >= HDR_ABS_MIN && doff + dcount <= buf.len() {
150            buf[doff..doff + dcount].to_vec()
151        } else {
152            Vec::new()
153        };
154
155        Some(Trans2Req {
156            subcmd,
157            params: fixed,
158            data,
159            param_base,
160            data_base: doff,
161            unicode,
162        })
163    }
164}
165
166/// Minimum bytes before a data section may plausibly begin.
167const HDR_ABS_MIN: usize = 32;
168
169/// Build the WC=10 TRANS2 response with even-aligned Param/Data offsets
170/// ([MS-CIFS] §2.2.4.46.2 layout used by every subcommand reply).
171pub fn trans2_resp(params: Vec<u8>, data: Vec<u8>) -> (Vec<u8>, Vec<u8>) {
172    const WORDS_BYTES: usize = 20;
173    const BC_START: usize = 32 + 1 + WORDS_BYTES + 2; // 55
174
175    let plen = params.len();
176    let dlen = data.len();
177    let poff = if plen > 0 { (BC_START + 1) & !1 } else { 0 };
178    let raw_doff = if plen > 0 { poff + plen } else { BC_START };
179    let doff = if dlen > 0 { (raw_doff + 1) & !1 } else { 0 };
180
181    let mut p = Vec::with_capacity(WORDS_BYTES);
182    p.extend_from_slice(&(plen as u16).to_le_bytes()); // TotalParameterCount
183    p.extend_from_slice(&(dlen as u16).to_le_bytes()); // TotalDataCount
184    p.extend_from_slice(&0u16.to_le_bytes()); // Reserved
185    p.extend_from_slice(&(plen as u16).to_le_bytes()); // ParameterCount
186    p.extend_from_slice(&(poff as u16).to_le_bytes()); // ParameterOffset
187    p.extend_from_slice(&0u16.to_le_bytes()); // ParameterDisplacement
188    p.extend_from_slice(&(dlen as u16).to_le_bytes()); // DataCount
189    p.extend_from_slice(&(doff as u16).to_le_bytes()); // DataOffset
190    p.extend_from_slice(&0u16.to_le_bytes()); // DataDisplacement
191    p.push(0); // SetupCount
192    p.push(0); // Reserved
193
194    debug_assert_eq!(p.len(), WORDS_BYTES);
195
196    let mut bytes = Vec::new();
197    if plen > 0 {
198        bytes.resize(poff - BC_START, 0);
199        bytes.extend_from_slice(&params);
200    }
201    if dlen > 0 {
202        bytes.resize(doff - BC_START, 0);
203        bytes.extend_from_slice(&data);
204    }
205    (p, bytes)
206}
207
208/// Read a NUL-terminated string out of the fixed-parameters area at `pos`,
209/// using `param_base` as parity base.
210pub fn read_param_zstring<'a>(
211    rd: &mut Reader<'a>,
212    _pos: usize,
213    param_base: usize,
214    unicode: bool,
215) -> String {
216    rd.zstring(unicode, param_base)
217}