smb_msg/
negotiate.rs

1use binrw::io::{SeekFrom, TakeSeekExt};
2use binrw::prelude::*;
3use modular_bitfield::prelude::*;
4
5use smb_dtyp::{binrw_util::prelude::*, guid::Guid};
6
7#[binrw::binrw]
8#[derive(Debug)]
9pub struct NegotiateRequest {
10    #[bw(calc = 0x24)]
11    #[br(assert(_structure_size == 0x24))]
12    _structure_size: u16,
13    #[bw(try_calc(u16::try_from(dialects.len())))]
14    dialect_count: u16,
15    pub security_mode: NegotiateSecurityMode,
16    #[bw(calc = 0)]
17    #[br(assert(_reserved == 0))]
18    _reserved: u16,
19    pub capabilities: GlobalCapabilities,
20    pub client_guid: Guid,
21
22    #[bw(calc = PosMarker::default())]
23    negotiate_context_offset: PosMarker<u32>,
24    #[bw(try_calc(u16::try_from(negotiate_context_list.as_ref().map(|v| v.len()).unwrap_or(0))))]
25    negotiate_context_count: u16,
26    #[bw(calc = 0)]
27    #[br(assert(reserved2 == 0))]
28    reserved2: u16,
29    #[br(count = dialect_count)]
30    pub dialects: Vec<Dialect>,
31    // Only on SMB 3.1.1 supporting clients we have negotiation contexts.
32    // Align to 8 bytes.
33    #[brw(if(dialects.contains(&Dialect::Smb0311)), align_before = 8)]
34    #[br(count = negotiate_context_count, seek_before = SeekFrom::Start(negotiate_context_offset.value as u64))]
35    #[bw(write_with = PosMarker::write_aoff, args(&negotiate_context_offset))]
36    pub negotiate_context_list: Option<Vec<NegotiateContext>>,
37}
38
39#[bitfield]
40#[derive(BinRead, BinWrite, Debug, Default, Clone, Copy, PartialEq, Eq)]
41#[bw(map = |&x| Self::into_bytes(x))]
42#[br(map = Self::from_bytes)]
43pub struct NegotiateSecurityMode {
44    pub signing_enabled: bool,
45    pub signing_required: bool,
46    #[skip]
47    __: B14,
48}
49
50#[bitfield]
51#[derive(BinRead, BinWrite, Debug, Default, Clone, Copy, PartialEq, Eq)]
52#[bw(map = |&x| Self::into_bytes(x))]
53#[br(map = Self::from_bytes)]
54pub struct GlobalCapabilities {
55    pub dfs: bool,
56    pub leasing: bool,
57    pub large_mtu: bool,
58    pub multi_channel: bool,
59
60    pub persistent_handles: bool,
61    pub directory_leasing: bool,
62    pub encryption: bool,
63    pub notifications: bool,
64
65    #[skip]
66    __: B24,
67}
68
69#[binrw::binrw]
70#[derive(Debug, PartialEq, Eq)]
71pub struct NegotiateResponse {
72    #[br(assert(_structure_size == 0x41))]
73    #[bw(calc = 0x41)]
74    _structure_size: u16,
75    pub security_mode: NegotiateSecurityMode,
76    pub dialect_revision: NegotiateDialect,
77    #[bw(try_calc(u16::try_from(negotiate_context_list.as_ref().map(|v| v.len()).unwrap_or(0))))]
78    #[br(assert(if dialect_revision == NegotiateDialect::Smb0311 { negotiate_context_count > 0 } else { negotiate_context_count == 0 }))]
79    negotiate_context_count: u16,
80    pub server_guid: Guid,
81    pub capabilities: GlobalCapabilities,
82    pub max_transact_size: u32,
83    pub max_read_size: u32,
84    pub max_write_size: u32,
85    pub system_time: FileTime,
86    pub server_start_time: FileTime,
87    #[bw(calc = PosMarker::default())]
88    _security_buffer_offset: PosMarker<u16>,
89    #[bw(try_calc(u16::try_from(buffer.len())))]
90    security_buffer_length: u16,
91    #[bw(calc = PosMarker::default())]
92    negotiate_context_offset: PosMarker<u32>,
93    #[br(count = security_buffer_length)]
94    #[bw(write_with = PosMarker::write_aoff, args(&_security_buffer_offset))]
95    pub buffer: Vec<u8>,
96
97    #[brw(if(matches!(dialect_revision, NegotiateDialect::Smb0311)), align_before = 8)]
98    #[br(count = negotiate_context_count, seek_before = SeekFrom::Start(negotiate_context_offset.value as u64))]
99    #[bw(write_with = PosMarker::write_aoff, args(&negotiate_context_offset))]
100    pub negotiate_context_list: Option<Vec<NegotiateContext>>,
101}
102
103impl NegotiateResponse {
104    pub fn get_ctx_signing_algo(&self) -> Option<SigningAlgorithmId> {
105        self.negotiate_context_list.as_ref().and_then(|contexts| {
106            contexts
107                .iter()
108                .find_map(|context| match &context.context_type {
109                    NegotiateContextType::SigningCapabilities => match &context.data {
110                        NegotiateContextValue::SigningCapabilities(caps) => {
111                            caps.signing_algorithms.first().copied()
112                        }
113                        _ => None,
114                    },
115                    _ => None,
116                })
117        })
118    }
119
120    pub fn get_ctx_integrity_algo(&self) -> Option<HashAlgorithm> {
121        self.negotiate_context_list.as_ref().and_then(|contexts| {
122            contexts
123                .iter()
124                .find_map(|context| match &context.context_type {
125                    NegotiateContextType::PreauthIntegrityCapabilities => match &context.data {
126                        NegotiateContextValue::PreauthIntegrityCapabilities(caps) => {
127                            caps.hash_algorithms.first().copied()
128                        }
129                        _ => None,
130                    },
131                    _ => None,
132                })
133        })
134    }
135
136    pub fn get_ctx_compression(&self) -> Option<&CompressionCapabilities> {
137        self.negotiate_context_list.as_ref().and_then(|contexts| {
138            contexts
139                .iter()
140                .find_map(|context| match &context.context_type {
141                    NegotiateContextType::CompressionCapabilities => match &context.data {
142                        NegotiateContextValue::CompressionCapabilities(caps) => Some(caps),
143                        _ => None,
144                    },
145                    _ => None,
146                })
147        })
148    }
149
150    pub fn get_ctx_encrypt_cipher(&self) -> Option<EncryptionCipher> {
151        self.negotiate_context_list.as_ref().and_then(|contexts| {
152            contexts
153                .iter()
154                .find_map(|context| match &context.context_type {
155                    NegotiateContextType::EncryptionCapabilities => match &context.data {
156                        NegotiateContextValue::EncryptionCapabilities(caps) => {
157                            caps.ciphers.first().copied()
158                        }
159                        _ => None,
160                    },
161                    _ => None,
162                })
163        })
164    }
165}
166
167#[derive(BinRead, BinWrite, Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
168#[brw(repr(u16))]
169pub enum Dialect {
170    Smb0202 = 0x0202,
171    Smb021 = 0x0210,
172    Smb030 = 0x0300,
173    Smb0302 = 0x0302,
174    Smb0311 = 0x0311,
175}
176
177impl Dialect {
178    pub const MAX: Dialect = Dialect::Smb0311;
179    pub const MIN: Dialect = Dialect::Smb0202;
180    pub const ALL: [Dialect; 5] = [
181        Dialect::Smb0202,
182        Dialect::Smb021,
183        Dialect::Smb030,
184        Dialect::Smb0302,
185        Dialect::Smb0311,
186    ];
187
188    #[inline]
189    pub fn is_smb3(&self) -> bool {
190        matches!(self, Dialect::Smb030 | Dialect::Smb0302 | Dialect::Smb0311)
191    }
192}
193
194/// Dialects that may be used in the SMB Negotiate Request.
195/// The same as [Dialect] but with a wildcard for SMB 2.0.
196#[derive(BinRead, BinWrite, Debug, PartialEq, Eq, Clone, Copy)]
197#[brw(repr(u16))]
198pub enum NegotiateDialect {
199    Smb0202 = Dialect::Smb0202 as isize,
200    Smb021 = Dialect::Smb021 as isize,
201    Smb030 = Dialect::Smb030 as isize,
202    Smb0302 = Dialect::Smb0302 as isize,
203    Smb0311 = Dialect::Smb0311 as isize,
204    Smb02Wildcard = 0x02FF,
205}
206
207impl TryFrom<NegotiateDialect> for Dialect {
208    type Error = crate::SmbMsgError;
209
210    fn try_from(value: NegotiateDialect) -> Result<Self, Self::Error> {
211        match value {
212            NegotiateDialect::Smb0202 => Ok(Dialect::Smb0202),
213            NegotiateDialect::Smb021 => Ok(Dialect::Smb021),
214            NegotiateDialect::Smb030 => Ok(Dialect::Smb030),
215            NegotiateDialect::Smb0302 => Ok(Dialect::Smb0302),
216            NegotiateDialect::Smb0311 => Ok(Dialect::Smb0311),
217            _ => Err(Self::Error::InvalidDialect(value)),
218        }
219    }
220}
221
222#[binrw::binrw]
223#[derive(Debug, PartialEq, Eq)]
224pub struct NegotiateContext {
225    // The entire context is 8-byte aligned.
226    #[brw(align_before = 8)]
227    pub context_type: NegotiateContextType,
228    #[bw(calc = PosMarker::default())]
229    data_length: PosMarker<u16>,
230    #[bw(calc = 0)]
231    #[br(assert(_reserved == 0))]
232    _reserved: u32,
233    #[br(args(&context_type))]
234    #[br(map_stream = |s| s.take_seek(data_length.value as u64))]
235    #[bw(write_with = PosMarker::write_size, args(&data_length))]
236    pub data: NegotiateContextValue,
237}
238
239macro_rules! negotiate_context_type {
240    ($($name:ident = $id:literal,)+) => {
241#[derive(BinRead, BinWrite, Debug, PartialEq, Eq)]
242#[brw(repr(u16))]
243pub enum NegotiateContextType {
244    $(
245        $name = $id,
246    )+
247}
248
249#[derive(BinRead, BinWrite, Debug, PartialEq, Eq)]
250#[br(import(context_type: &NegotiateContextType))]
251pub enum NegotiateContextValue {
252    $(
253        #[br(pre_assert(context_type == &NegotiateContextType::$name))]
254        $name($name),
255    )+
256}
257
258impl NegotiateContextValue {
259    pub fn get_matching_type(&self) -> NegotiateContextType {
260        match self {
261            $(
262                NegotiateContextValue::$name(_) => {
263                    NegotiateContextType::$name
264                }
265            )+
266        }
267    }
268}
269    };
270}
271
272negotiate_context_type!(
273    PreauthIntegrityCapabilities = 0x0001,
274    EncryptionCapabilities = 0x0002,
275    CompressionCapabilities = 0x0003,
276    NetnameNegotiateContextId = 0x0005,
277    TransportCapabilities = 0x0006,
278    RdmaTransformCapabilities = 0x0007,
279    SigningCapabilities = 0x0008,
280);
281
282impl From<NegotiateContextValue> for NegotiateContext {
283    fn from(val: NegotiateContextValue) -> Self {
284        NegotiateContext {
285            context_type: val.get_matching_type(),
286            data: val,
287        }
288    }
289}
290
291// u16 enum hash algorithms binrw 0x01 is sha512.
292#[derive(BinRead, BinWrite, Debug, PartialEq, Eq, Clone, Copy)]
293#[brw(repr(u16))]
294pub enum HashAlgorithm {
295    Sha512 = 0x01,
296}
297
298#[binrw::binrw]
299#[derive(Debug, PartialEq, Eq)]
300pub struct PreauthIntegrityCapabilities {
301    #[bw(try_calc(u16::try_from(hash_algorithms.len())))]
302    hash_algorithm_count: u16,
303    #[bw(try_calc(u16::try_from(salt.len())))]
304    salt_length: u16,
305    #[br(count = hash_algorithm_count)]
306    pub hash_algorithms: Vec<HashAlgorithm>,
307    #[br(count = salt_length)]
308    pub salt: Vec<u8>,
309}
310
311#[binrw::binrw]
312#[derive(Debug, PartialEq, Eq)]
313pub struct EncryptionCapabilities {
314    #[bw(try_calc(u16::try_from(ciphers.len())))]
315    cipher_count: u16,
316    #[br(count = cipher_count)]
317    pub ciphers: Vec<EncryptionCipher>,
318}
319
320#[derive(BinRead, BinWrite, Debug, PartialEq, Eq, Clone, Copy)]
321#[brw(repr(u16))]
322pub enum EncryptionCipher {
323    Aes128Ccm = 0x0001,
324    Aes128Gcm = 0x0002,
325    Aes256Ccm = 0x0003,
326    Aes256Gcm = 0x0004,
327}
328
329#[binrw::binrw]
330#[derive(Debug, PartialEq, Eq, Clone)]
331pub struct CompressionCapabilities {
332    #[bw(try_calc(u16::try_from(compression_algorithms.len())))]
333    compression_algorithm_count: u16,
334    #[bw(calc = 0)]
335    _padding: u16,
336    pub flags: CompressionCapsFlags,
337    #[br(count = compression_algorithm_count)]
338    pub compression_algorithms: Vec<CompressionAlgorithm>,
339}
340
341#[derive(BinRead, BinWrite, Debug, PartialEq, Eq, Clone, Copy)]
342#[brw(repr(u16))]
343#[repr(u16)]
344pub enum CompressionAlgorithm {
345    None = 0x0000,
346    LZNT1 = 0x0001,
347    LZ77 = 0x0002,
348    LZ77Huffman = 0x0003,
349    PatternV1 = 0x0004,
350    LZ4 = 0x0005,
351}
352
353impl CompressionAlgorithm {
354    /// Relevant for processing compressed messages.
355    pub fn original_size_required(&self) -> bool {
356        matches!(
357            self,
358            CompressionAlgorithm::LZNT1
359                | CompressionAlgorithm::LZ77
360                | CompressionAlgorithm::LZ77Huffman
361                | CompressionAlgorithm::LZ4
362        )
363    }
364}
365
366impl std::fmt::Display for CompressionAlgorithm {
367    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
368        let message_as_string = match self {
369            CompressionAlgorithm::None => "None",
370            CompressionAlgorithm::LZNT1 => "LZNT1",
371            CompressionAlgorithm::LZ77 => "LZ77",
372            CompressionAlgorithm::LZ77Huffman => "LZ77+Huffman",
373            CompressionAlgorithm::PatternV1 => "PatternV1",
374            CompressionAlgorithm::LZ4 => "LZ4",
375        };
376        write!(f, "{} ({:#x})", message_as_string, *self as u16)
377    }
378}
379
380#[bitfield]
381#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)]
382#[bw(map = |&x| Self::into_bytes(x))]
383#[br(map = Self::from_bytes)]
384pub struct CompressionCapsFlags {
385    pub chained: bool,
386    #[skip]
387    __: B31,
388}
389
390#[derive(BinRead, BinWrite, Debug, PartialEq, Eq)]
391pub struct NetnameNegotiateContextId {
392    #[br(parse_with = binrw::helpers::until_eof)]
393    pub netname: SizedWideString,
394}
395
396#[bitfield]
397#[derive(BinWrite, BinRead, Debug, Default, Clone, Copy, PartialEq, Eq)]
398#[bw(map = |&x| Self::into_bytes(x))]
399#[br(map = Self::from_bytes)]
400pub struct TransportCapabilities {
401    pub accept_transport_layer_security: bool,
402    #[skip]
403    __: B31,
404}
405
406#[binrw::binrw]
407#[derive(Debug, PartialEq, Eq)]
408pub struct RdmaTransformCapabilities {
409    #[bw(try_calc(u16::try_from(transforms.len())))]
410    transform_count: u16,
411
412    #[bw(calc = 0)]
413    #[br(assert(reserved1 == 0))]
414    reserved1: u16,
415    #[bw(calc = 0)]
416    #[br(assert(reserved2 == 0))]
417    reserved2: u32,
418
419    #[br(count = transform_count)]
420    pub transforms: Vec<RdmaTransformId>,
421}
422
423#[binrw::binrw]
424#[derive(Debug, PartialEq, Eq, Clone, Copy)]
425#[brw(repr(u16))]
426pub enum RdmaTransformId {
427    None = 0x0000,
428    Encryption = 0x0001,
429    Signing = 0x0002,
430}
431
432#[binrw::binrw]
433#[derive(Debug, PartialEq, Eq)]
434pub struct SigningCapabilities {
435    #[bw(try_calc(u16::try_from(signing_algorithms.len())))]
436    signing_algorithm_count: u16,
437    #[br(count = signing_algorithm_count)]
438    pub signing_algorithms: Vec<SigningAlgorithmId>,
439}
440
441#[derive(BinRead, BinWrite, Debug, PartialEq, Eq, Clone, Copy)]
442#[brw(repr(u16))]
443pub enum SigningAlgorithmId {
444    HmacSha256 = 0x0000,
445    AesCmac = 0x0001,
446    AesGmac = 0x0002,
447}
448
449#[cfg(test)]
450mod tests {
451    use time::macros::datetime;
452
453    use super::*;
454    use crate::*;
455
456    #[test]
457    pub fn test_negotiate_res_parse() {
458        let data = [
459            0xfe, 0x53, 0x4d, 0x42, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0,
460            0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff,
461            0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
462            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x41, 0x0, 0x1,
463            0x0, 0x11, 0x3, 0x5, 0x0, 0xb9, 0x21, 0xf8, 0xe0, 0x15, 0x7, 0xaa, 0x41, 0xbe, 0x38,
464            0x67, 0xfe, 0xbf, 0x5e, 0x2e, 0x11, 0x2f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x0, 0x0, 0x0,
465            0x80, 0x0, 0x0, 0x0, 0x80, 0x0, 0xa8, 0x76, 0xd8, 0x78, 0xc5, 0x69, 0xdb, 0x1, 0x0,
466            0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x80, 0x0, 0x2a, 0x0, 0xb0, 0x0, 0x0, 0x0, 0x60,
467            0x28, 0x6, 0x6, 0x2b, 0x6, 0x1, 0x5, 0x5, 0x2, 0xa0, 0x1e, 0x30, 0x1c, 0xa0, 0x1a,
468            0x30, 0x18, 0x6, 0xa, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x82, 0x37, 0x2, 0x2, 0x1e, 0x6, 0xa,
469            0x2b, 0x6, 0x1, 0x4, 0x1, 0x82, 0x37, 0x2, 0x2, 0xa, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
470            0x0, 0x26, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x20, 0x0, 0x1, 0x0, 0xd5, 0x67, 0x1b,
471            0x24, 0xa1, 0xe9, 0xcc, 0xc8, 0x93, 0xf5, 0x55, 0x5a, 0x31, 0x3, 0x43, 0x5a, 0x85,
472            0x2b, 0xc3, 0xcb, 0x1a, 0xd3, 0x2d, 0xc5, 0x1f, 0x92, 0x80, 0x6e, 0xf3, 0xfb, 0x4d,
473            0xd4, 0x0, 0x0, 0x2, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x2, 0x0, 0x0, 0x0,
474            0x0, 0x0, 0x8, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0,
475            0x0, 0x7, 0x0, 0xc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
476            0x1, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0x0, 0xc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2,
477            0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x2, 0x0, 0x4, 0x0,
478        ];
479
480        let response = decode_content(&data).content.to_negotiate().unwrap();
481
482        assert_eq!(
483            response,
484            NegotiateResponse {
485                security_mode: NegotiateSecurityMode::new().with_signing_enabled(true),
486                dialect_revision: NegotiateDialect::Smb0311,
487                server_guid: Guid::from([
488                    0xb9, 0x21, 0xf8, 0xe0, 0x15, 0x7, 0xaa, 0x41, 0xbe, 0x38, 0x67, 0xfe, 0xbf,
489                    0x5e, 0x2e, 0x11
490                ]),
491                capabilities: GlobalCapabilities::new()
492                    .with_dfs(true)
493                    .with_leasing(true)
494                    .with_large_mtu(true)
495                    .with_multi_channel(true)
496                    .with_directory_leasing(true),
497                max_transact_size: 8388608,
498                max_read_size: 8388608,
499                max_write_size: 8388608,
500                system_time: datetime!(2025-01-18 16:24:39.448746400).into(),
501                server_start_time: FileTime::default(),
502                buffer: [
503                    0x60, 0x28, 0x6, 0x6, 0x2b, 0x6, 0x1, 0x5, 0x5, 0x2, 0xa0, 0x1e, 0x30, 0x1c,
504                    0xa0, 0x1a, 0x30, 0x18, 0x6, 0xa, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x82, 0x37, 0x2,
505                    0x2, 0x1e, 0x6, 0xa, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x82, 0x37, 0x2, 0x2, 0xa
506                ]
507                .to_vec(),
508                negotiate_context_list: Some(vec![
509                    NegotiateContextValue::PreauthIntegrityCapabilities(
510                        PreauthIntegrityCapabilities {
511                            hash_algorithms: vec![HashAlgorithm::Sha512],
512                            salt: [
513                                0xd5, 0x67, 0x1b, 0x24, 0xa1, 0xe9, 0xcc, 0xc8, 0x93, 0xf5, 0x55,
514                                0x5a, 0x31, 0x3, 0x43, 0x5a, 0x85, 0x2b, 0xc3, 0xcb, 0x1a, 0xd3,
515                                0x2d, 0xc5, 0x1f, 0x92, 0x80, 0x6e, 0xf3, 0xfb, 0x4d, 0xd4
516                            ]
517                            .to_vec()
518                        }
519                    )
520                    .into(),
521                    NegotiateContextValue::EncryptionCapabilities(EncryptionCapabilities {
522                        ciphers: vec![EncryptionCipher::Aes128Gcm]
523                    })
524                    .into(),
525                    NegotiateContextValue::SigningCapabilities(SigningCapabilities {
526                        signing_algorithms: vec![SigningAlgorithmId::AesGmac]
527                    })
528                    .into(),
529                    NegotiateContextValue::RdmaTransformCapabilities(RdmaTransformCapabilities {
530                        transforms: vec![RdmaTransformId::Encryption, RdmaTransformId::Signing]
531                    })
532                    .into(),
533                    NegotiateContextValue::CompressionCapabilities(CompressionCapabilities {
534                        flags: CompressionCapsFlags::new().with_chained(true),
535                        compression_algorithms: vec![
536                            CompressionAlgorithm::LZ77,
537                            CompressionAlgorithm::PatternV1
538                        ]
539                    })
540                    .into(),
541                ])
542            }
543        )
544    }
545}