Skip to main content

smb_server_proto_smb1/
session_setup.rs

1//! `SMB_COM_SESSION_SETUP_ANDX` ([MS-SMB] ยง2.2.4.6).
2//!
3//! Two request shapes exist on this command:
4//! * **WC=13** โ€” legacy non-extended security (password blobs + account).
5//! * **WC=12** โ€” extended security ([MS-NLMP] via SPNEGO): the buffer holds
6//!   an opaque `SecurityBlob` instead of passwords.
7
8use smb_server_proto::buf::Reader;
9
10/// Parse error for body decoders.
11#[derive(Debug)]
12pub enum BodyError {
13    /// Word/parameter block too small for the command.
14    TooShort,
15    /// Password/blob length exceeded the available buffer.
16    BadBlob,
17}
18
19fn u16le(w: &[u8], off: usize) -> u16 {
20    w.get(off..off + 2)
21        .map(|s| u16::from_le_bytes(s.try_into().unwrap()))
22        .unwrap_or(0)
23}
24
25/// Parsed legacy (WC=13) session setup request.
26#[derive(Debug)]
27pub struct SessionSetupLegacyReq {
28    /// Case-insensitive (LM) password response bytes.
29    pub lm_resp: Vec<u8>,
30    /// Case-sensitive (NTLM/NTLMv2) response bytes.
31    pub nt_resp: Vec<u8>,
32    /// Requested account name.
33    pub account: String,
34    /// Client primary domain string.
35    pub domain: String,
36}
37
38impl SessionSetupLegacyReq {
39    /// Parse WC=13 words: CIPLen@14, CSPLen@16, Capabilities@22.
40    pub fn parse(
41        words: &[u8],
42        data: &[u8],
43        unicode: bool,
44        data_base: usize,
45    ) -> Result<Self, BodyError> {
46        if words.len() < 26 || data.len() < 2 {
47            return Err(BodyError::TooShort);
48        }
49        let cip = u16le(words, 14) as usize;
50        let csp = u16le(words, 16) as usize;
51        if cip.checked_add(csp).map(|t| t > data.len()).unwrap_or(true) {
52            return Err(BodyError::BadBlob);
53        }
54        let lm_resp = data[..cip].to_vec();
55        let nt_resp = data[cip..cip + csp].to_vec();
56
57        let mut rd = Reader::new(data, cip + csp);
58        let account = rd.zstring(unicode, data_base);
59        let domain = rd.zstring(unicode, data_base);
60        Ok(Self { lm_resp, nt_resp, account, domain })
61    }
62}
63
64/// Parsed extended (WC=12) session setup request: opaque security blob.
65#[derive(Debug)]
66pub struct SessionSetupExtReq {
67    /// SPNEGO/NTLMSSP token bytes.
68    pub blob: Vec<u8>,
69}
70
71impl SessionSetupExtReq {
72    /// Parse WC=12 words: SecurityBlobLength @vwv7 (bytes 14โ€“15).
73    pub fn parse(words: &[u8], data: &[u8]) -> Result<Self, BodyError> {
74        if words.len() < 16 || data.is_empty() {
75            return Err(BodyError::TooShort);
76        }
77        let blen = u16le(words, 14) as usize;
78        if blen == 0 || blen > data.len() {
79            return Err(BodyError::BadBlob);
80        }
81        Ok(SessionSetupExtReq { blob: data[..blen].to_vec() })
82    }
83}
84
85/// Build a WC=4 session setup response (blob + OEM strings).
86pub fn build_session_setup_response(action: u16, blob: &[u8]) -> (Vec<u8>, Vec<u8>) {
87    build_response(action, blob)
88}
89
90/// Build a WC=4 session setup response: AndX triple, Action word,
91/// SecurityBlobLength word, then `[blob][NativeOS][NativeLanMan]` data.
92///
93/// Trailing strings are always OEM (some clients mis-parse UTF-16 here).
94pub fn build_response(action: u16, blob: &[u8]) -> (Vec<u8>, Vec<u8>) {
95    let mut params = Vec::with_capacity(8);
96    params.push(0xFF); // terminal AndX
97    params.push(0);
98    params.extend_from_slice(&0u16.to_le_bytes()); // AndXOffset
99    params.extend_from_slice(&action.to_le_bytes()); // Action
100    params.extend_from_slice(&(blob.len() as u16).to_le_bytes());
101
102    let mut bytes = Vec::with_capacity(blob.len() + 18);
103    bytes.extend_from_slice(blob);
104    bytes.extend_from_slice(b"rustsmb\0rustsmb\0");
105    (params, bytes)
106}