Skip to main content

smb_server_proto_smb1/
header.rs

1//! SMB header codec ([MS-SMB] §2.2.3.1, base layout [MS-CIFS] §2.2.1.1) and
2//! AndX-aware response assembly.
3
4use smb_server_proto::types::Status;
5
6pub use crate::consts::{flags, flags2};
7
8/// The 32-byte SMB header shared by every message.
9#[derive(Debug, Clone)]
10pub struct Header {
11    /// Command opcode.
12    pub command: u8,
13    /// NT status (or DOS error packed into 4 bytes).
14    pub status: Status,
15    /// Flags byte.
16    pub flags: u8,
17    /// Flags2 word.
18    pub flags2: u16,
19    /// High half of the multiplexed PID.
20    pub pid_high: u16,
21    /// Tree identifier.
22    pub tid: u16,
23    /// Process identifier (low half).
24    pub pid: u16,
25    /// User identifier.
26    pub uid: u16,
27    /// Multiplex identifier.
28    pub mid: u16,
29}
30
31/// Size of the fixed SMB header.
32pub const HDR_LEN: usize = 32;
33
34/// Magic bytes `\xFFSMB`.
35pub const SMB_MAGIC: [u8; 4] = [0xFF, b'S', b'M', b'B'];
36
37/// Parse a header from the start of `buf`; returns the header plus the
38/// WordCount offset (always [`HDR_LEN`]). `None` on bad magic/size.
39pub fn parse_header(buf: &[u8]) -> Option<(Header, usize)> {
40    if buf.len() < HDR_LEN + 1 || buf[0..4] != SMB_MAGIC {
41        return None;
42    }
43    let hdr = Header {
44        command: buf[4],
45        status: Status(u32::from_le_bytes(buf[5..9].try_into().ok()?)),
46        flags: buf[9],
47        flags2: u16::from_le_bytes(buf[10..12].try_into().ok()?),
48        pid_high: u16::from_le_bytes(buf[12..14].try_into().ok()?),
49        tid: u16::from_le_bytes(buf[24..26].try_into().ok()?),
50        pid: u16::from_le_bytes(buf[26..28].try_into().ok()?),
51        uid: u16::from_le_bytes(buf[28..30].try_into().ok()?),
52        mid: u16::from_le_bytes(buf[30..32].try_into().ok()?),
53    };
54    Some((hdr, HDR_LEN))
55}
56
57/// One response body (parameter words + data block). A complete frame carries
58/// a header plus one body; AndX chains carry several bodies whose parameter
59/// blocks open with AndXCommand/AndXOffset links.
60#[derive(Debug, Clone)]
61pub struct RespBody {
62    /// Command this body responds to.
63    pub command: u8,
64    /// Parameter words (even length; AndX fields live at bytes 0–3).
65    pub params: Vec<u8>,
66    /// Data block following ByteCount.
67    pub bytes: Vec<u8>,
68    /// Overrides the response header TID (tree connect returns a fresh TID).
69    pub tid_override: Option<u16>,
70    /// Overrides the response header UID (session setup returns a fresh UID).
71    pub uid_override: Option<u16>,
72}
73
74impl RespBody {
75    /// Build a body without ID overrides.
76    pub fn new(command: u8, params: Vec<u8>, bytes: Vec<u8>) -> Self {
77        RespBody { command, params, bytes, tid_override: None, uid_override: None }
78    }
79}
80
81fn is_andx_capable(cmd: u8) -> bool {
82    matches!(
83        cmd,
84        crate::consts::COM_SESSION_SETUP_ANDX
85            | crate::consts::COM_TREE_CONNECT_ANDX
86            | crate::consts::COM_READ_ANDX
87            | crate::consts::COM_WRITE_ANDX
88            | crate::consts::COM_LOGOFF_ANDX
89            | crate::consts::COM_LOCKING_ANDX
90            | crate::consts::COM_NT_CREATE_ANDX
91    )
92}
93
94/// Assemble a complete SMB frame echoing `req_hdr`, carrying `status` and the
95/// given bodies, wiring AndX offsets between them.
96///
97/// An empty `bodies` list produces a bare error frame for the request's own
98/// command so header-only rejections still reach the client.
99pub fn build_response(req_hdr: &Header, status: Status, mut bodies: Vec<RespBody>) -> Vec<u8> {
100    if bodies.is_empty() {
101        bodies.push(RespBody::new(req_hdr.command, Vec::new(), Vec::new()));
102    }
103
104    // Terminal AndX bodies: AndXCommand=0xFF, AndXOffset=0 (as real servers).
105    for i in 0..bodies.len() {
106        if is_andx_capable(bodies[i].command)
107            && bodies[i].params.len() >= 4
108            && i + 1 >= bodies.len()
109        {
110            bodies[i].params[0] = 0xFF;
111            bodies[i].params[1] = 0;
112            bodies[i].params[2..4].copy_from_slice(&0u16.to_le_bytes());
113        }
114    }
115
116    let mut out = Vec::with_capacity(HDR_LEN + 64);
117    out.extend_from_slice(&SMB_MAGIC);
118    out.push(bodies[0].command);
119    if req_hdr.flags2 & flags2::NT_STATUS != 0 {
120        out.extend_from_slice(&status.raw().to_le_bytes());
121    } else {
122        let (class, code) = status.to_dos();
123        out.extend_from_slice(&[code, 0, class, 0]);
124    }
125    out.push(flags::RESPONSE | (req_hdr.flags & flags::CASE_SENSITIVE));
126    let mut flags2 = req_hdr.flags2 & (flags2::UNICODE | flags2::LONG_NAMES | flags2::NT_STATUS);
127    flags2 |= flags2::LONG_NAMES;
128    out.extend_from_slice(&flags2.to_le_bytes());
129    out.extend_from_slice(&req_hdr.pid_high.to_le_bytes());
130    out.extend_from_slice(&[0u8; 8]); // signature — signing unsupported
131    out.extend_from_slice(&[0u8; 2]); // reserved
132    out.extend_from_slice(&bodies[0].tid_override.unwrap_or(req_hdr.tid).to_le_bytes());
133    out.extend_from_slice(&req_hdr.pid.to_le_bytes());
134    out.extend_from_slice(&bodies[0].uid_override.unwrap_or(req_hdr.uid).to_le_bytes());
135    out.extend_from_slice(&req_hdr.mid.to_le_bytes());
136
137    for (i, body) in bodies.iter().enumerate() {
138        let body_start = out.len();
139        debug_assert!(body.params.len() % 2 == 0);
140        out.push((body.params.len() / 2) as u8);
141        out.extend_from_slice(&body.params);
142        out.extend_from_slice(&(body.bytes.len() as u16).to_le_bytes());
143        out.extend_from_slice(&body.bytes);
144
145        if is_andx_capable(body.command) && body.params.len() >= 4 {
146            match bodies.get(i + 1) {
147                Some(next) => {
148                    let next_off = out.len();
149                    out[body_start + 1] = next.command;
150                    out[body_start + 2] = 0;
151                    out[body_start + 3..body_start + 5]
152                        .copy_from_slice(&(next_off as u16).to_le_bytes());
153                }
154                None => {
155                    // Terminal: 0xFF/0/0 already patched above.
156                    let end = out.len() as u16;
157                    out[body_start + 3..body_start + 5].copy_from_slice(&end.to_le_bytes());
158                    out[body_start + 1] = 0xFF;
159                }
160            }
161        }
162    }
163    out
164}