Skip to main content

smb_server_proto_smb1/
negotiate.rs

1//! `SMB_COM_NEGOTIATE` ([MS-SMB] §2.2.4.5).
2
3/// Parsed client request (§2.2.4.5.1): the offered dialect list.
4#[derive(Debug)]
5pub struct NegotiateReq {
6    /// Dialect strings offered by the client.
7    pub dialects: Vec<String>,
8}
9
10impl NegotiateReq {
11    /// Parse the dialect list from the request data area
12    /// (each entry: BufferFormat `0x02` + NUL-terminated name).
13    pub fn parse(data: &[u8]) -> Self {
14        let mut dialects = Vec::new();
15        let mut i = 0usize;
16        while i < data.len() && data[i] == 0x02 {
17            i += 1;
18            match data[i..].iter().position(|&b| b == 0) {
19                Some(p) => {
20                    let end = i + p;
21                    dialects.push(String::from_utf8_lossy(&data[i..end]).to_string());
22                    i = end + 1;
23                }
24                None => break,
25            }
26        }
27        NegotiateReq { dialects }
28    }
29
30    /// Pick the highest dialect we support; returns `(index, name)`.
31    pub fn select(&self) -> Option<usize> {
32        self.dialects
33            .iter()
34            .position(|d| d.eq_ignore_ascii_case("NT LM 0.12"))
35    }
36}
37
38/// Server response (§2.2.4.5.2): WordCount 17 with the byte-packed layout —
39/// DialectIndex(2), SecurityMode(1), MaxMpx(2), VCs(2), MaxBuf(4), MaxRaw(4),
40/// SessionKey(4), Capabilities(4), SystemTime(8), TimeZone(2),
41/// ChallengeLength(1) = 34 bytes; challenge bytes follow ByteCount.
42pub const WORD_COUNT: u8 = 17;
43
44/// Build response parameter words (34 bytes incl. ChallengeLength).
45pub fn build_params(dialect_index: u16, caps: u32, now: smb_server_proto::types::FileTime) -> Vec<u8> {
46    let mut p = Vec::with_capacity(34);
47    p.extend_from_slice(&dialect_index.to_le_bytes()); // [0-1]
48    p.push(0x03); //                                            [2] USER|ENCRYPT
49    p.extend_from_slice(&16u16.to_le_bytes()); //                [3-4] MaxMpx
50    p.extend_from_slice(&1u16.to_le_bytes()); //                 [5-6] VCs
51    p.extend_from_slice(&65535u32.to_le_bytes()); //             [7-10] MaxBuffer
52    p.extend_from_slice(&65536u32.to_le_bytes()); //             [11-14] MaxRaw
53    p.extend_from_slice(&0x1234_5678u32.to_le_bytes()); //       [15-18] SessionKey
54    p.extend_from_slice(&caps.to_le_bytes()); //                 [19-22] Capabilities
55    p.extend_from_slice(&now.0.to_le_bytes()); //                [23-30] SystemTime
56    p.extend_from_slice(&0i16.to_le_bytes()); //                 [31-32] TimeZone UTC
57    p.push(8u8); //                                              [33] ChallengeLength
58    debug_assert_eq!(p.len(), 34);
59    p
60}
61
62/// Build the response data block (the raw 8-byte challenge).
63pub fn build_bytes(challenge: &[u8; 8]) -> Vec<u8> {
64    challenge.to_vec()
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn picks_nt_lm_dialect() {
73        let req = NegotiateReq::parse(
74            b"\x02NT LANMAN 1.0\x00\x02NT LM 0.12\x00",
75        );
76        assert_eq!(req.select().unwrap(), 1);
77    }
78}