1use binrw::io::{SeekFrom, TakeSeekExt};
2use binrw::prelude::*;
3use modular_bitfield::prelude::*;
4
5use smb_dtyp::{binrw_util::prelude::*, guid::Guid};
6use smb_msg_derive::*;
7
8#[smb_request(size = 36)]
15pub struct NegotiateRequest {
16 #[bw(try_calc(u16::try_from(dialects.len())))]
17 #[br(temp)]
18 dialect_count: u16,
19 pub security_mode: NegotiateSecurityMode,
21 reserved: u16,
22 pub capabilities: GlobalCapabilities,
24 pub client_guid: Guid,
26
27 #[bw(calc = PosMarker::default())]
28 #[br(temp)]
29 negotiate_context_offset: PosMarker<u32>,
30 #[bw(try_calc(u16::try_from(negotiate_context_list.as_ref().map(|v| v.len()).unwrap_or(0))))]
31 #[br(temp)]
32 negotiate_context_count: u16,
33 reserved: u16,
34 #[br(count = dialect_count)]
36 pub dialects: Vec<Dialect>,
37 #[brw(if(dialects.contains(&Dialect::Smb0311)), align_before = 8)]
39 #[br(count = negotiate_context_count, seek_before = SeekFrom::Start(negotiate_context_offset.value as u64))]
40 #[bw(write_with = PosMarker::write_aoff, args(&negotiate_context_offset))]
41 pub negotiate_context_list: Option<Vec<NegotiateContext>>,
42}
43
44#[smb_dtyp::mbitfield]
50pub struct NegotiateSecurityMode {
51 pub signing_enabled: bool,
53 pub signing_required: bool,
55 #[skip]
56 __: B14,
57}
58
59#[smb_dtyp::mbitfield]
65pub struct GlobalCapabilities {
66 pub dfs: bool,
68 pub leasing: bool,
70 pub large_mtu: bool,
72 pub multi_channel: bool,
74
75 pub persistent_handles: bool,
77 pub directory_leasing: bool,
79 pub encryption: bool,
81 pub notifications: bool,
83
84 #[skip]
85 __: B24,
86}
87
88#[smb_response(size = 65)]
94pub struct NegotiateResponse {
95 pub security_mode: NegotiateSecurityMode,
97 pub dialect_revision: NegotiateDialect,
99 #[bw(try_calc(u16::try_from(negotiate_context_list.as_ref().map(|v| v.len()).unwrap_or(0))))]
100 #[br(assert(if dialect_revision == NegotiateDialect::Smb0311 { negotiate_context_count > 0 } else { negotiate_context_count == 0 }))]
101 #[br(temp)]
102 negotiate_context_count: u16,
103 pub server_guid: Guid,
105 pub capabilities: GlobalCapabilities,
107 pub max_transact_size: u32,
109 pub max_read_size: u32,
111 pub max_write_size: u32,
113 pub system_time: FileTime,
115 pub server_start_time: FileTime,
117 #[bw(calc = PosMarker::default())]
118 #[br(temp)]
119 _security_buffer_offset: PosMarker<u16>,
120 #[bw(try_calc(u16::try_from(buffer.len())))]
121 #[br(temp)]
122 security_buffer_length: u16,
123 #[bw(calc = PosMarker::default())]
124 #[br(temp)]
125 negotiate_context_offset: PosMarker<u32>,
126 #[br(count = security_buffer_length)]
128 #[bw(write_with = PosMarker::write_aoff, args(&_security_buffer_offset))]
129 pub buffer: Vec<u8>,
130
131 #[brw(if(matches!(dialect_revision, NegotiateDialect::Smb0311)), align_before = 8)]
133 #[br(count = negotiate_context_count, seek_before = SeekFrom::Start(negotiate_context_offset.value as u64))]
134 #[bw(write_with = PosMarker::write_aoff, args(&negotiate_context_offset))]
135 pub negotiate_context_list: Option<Vec<NegotiateContext>>,
136}
137
138#[derive(BinRead, BinWrite, Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
142#[brw(repr(u16))]
143pub enum Dialect {
144 Smb0202 = 0x0202,
145 Smb021 = 0x0210,
146 Smb030 = 0x0300,
147 Smb0302 = 0x0302,
148 Smb0311 = 0x0311,
149}
150
151impl Dialect {
152 pub const MAX: Dialect = Dialect::Smb0311;
153 pub const MIN: Dialect = Dialect::Smb0202;
154 pub const ALL: [Dialect; 5] = [
155 Dialect::Smb0202,
156 Dialect::Smb021,
157 Dialect::Smb030,
158 Dialect::Smb0302,
159 Dialect::Smb0311,
160 ];
161
162 #[inline]
164 pub fn is_smb3(&self) -> bool {
165 self >= &Dialect::Smb030
166 }
167}
168
169#[derive(BinRead, BinWrite, Debug, PartialEq, Eq, Clone, Copy)]
175#[brw(repr(u16))]
176pub enum NegotiateDialect {
177 Smb0202 = Dialect::Smb0202 as isize,
178 Smb021 = Dialect::Smb021 as isize,
179 Smb030 = Dialect::Smb030 as isize,
180 Smb0302 = Dialect::Smb0302 as isize,
181 Smb0311 = Dialect::Smb0311 as isize,
182 Smb02Wildcard = 0x02FF,
183}
184
185impl TryFrom<NegotiateDialect> for Dialect {
186 type Error = crate::SmbMsgError;
187
188 fn try_from(value: NegotiateDialect) -> Result<Self, Self::Error> {
189 match value {
190 NegotiateDialect::Smb0202 => Ok(Dialect::Smb0202),
191 NegotiateDialect::Smb021 => Ok(Dialect::Smb021),
192 NegotiateDialect::Smb030 => Ok(Dialect::Smb030),
193 NegotiateDialect::Smb0302 => Ok(Dialect::Smb0302),
194 NegotiateDialect::Smb0311 => Ok(Dialect::Smb0311),
195 _ => Err(Self::Error::InvalidDialect(value)),
196 }
197 }
198}
199
200#[smb_message_binrw]
215pub struct NegotiateContext {
216 #[brw(align_before = 8)]
218 pub context_type: NegotiateContextType,
219 #[bw(calc = PosMarker::default())]
220 #[br(temp)]
221 data_length: PosMarker<u16>,
222 reserved: u32,
223 #[br(args(&context_type))]
225 #[br(map_stream = |s| s.take_seek(data_length.value as u64))]
226 #[bw(write_with = PosMarker::write_size, args(&data_length))]
227 pub data: NegotiateContextValue,
228}
229
230macro_rules! negotiate_context_type {
231 ($($name:ident = $id:literal,)+) => {
232#[derive(BinRead, BinWrite, Debug, PartialEq, Eq)]
236#[brw(repr(u16))]
237pub enum NegotiateContextType {
238 $(
239 $name = $id,
240 )+
241}
242
243#[derive(BinRead, BinWrite, Debug, PartialEq, Eq)]
247#[br(import(context_type: &NegotiateContextType))]
248pub enum NegotiateContextValue {
249 $(
250 #[br(pre_assert(context_type == &NegotiateContextType::$name))]
251 $name($name),
252 )+
253}
254
255impl NegotiateContextValue {
256 pub fn get_matching_type(&self) -> NegotiateContextType {
258 match self {
259 $(
260 NegotiateContextValue::$name(_) => {
261 NegotiateContextType::$name
262 }
263 )+
264 }
265 }
266}
267
268$(
269 impl From<$name> for NegotiateContext {
270 fn from(val: $name) -> Self {
271 NegotiateContext {
272 context_type: NegotiateContextType::$name,
273 data: NegotiateContextValue::$name(val),
274 }
275 }
276 }
277)+
278
279macro_rules! gen_impl_for_neg_msg_type {
281 ($msg_type:ident) => {
282
283impl $msg_type {
284 $(
285 pastey::paste! {
286 #[doc = concat!("Gets the negotiate context of type [`", stringify!($name), "`] if present.")]
287 pub fn [<get_ctx_ $name:snake>] (&self) -> Option<& $name> {
290 self.negotiate_context_list.as_ref().and_then(|contexts| {
291 contexts.iter().find_map(|context| match &context.context_type {
292 NegotiateContextType::$name => match &context.data {
293 NegotiateContextValue::$name(caps) => Some(caps),
294 _ => None,
295 },
296 _ => None,
297 })
298 })
299 }
300
301 }
302 )+
303}
304
305 }
306}
307
308gen_impl_for_neg_msg_type!(NegotiateRequest);
309gen_impl_for_neg_msg_type!(NegotiateResponse);
310 };
311}
312
313negotiate_context_type!(
314 PreauthIntegrityCapabilities = 0x0001,
315 EncryptionCapabilities = 0x0002,
316 CompressionCapabilities = 0x0003,
317 NetnameNegotiateContextId = 0x0005,
318 TransportCapabilities = 0x0006,
319 RdmaTransformCapabilities = 0x0007,
320 SigningCapabilities = 0x0008,
321);
322
323#[derive(BinRead, BinWrite, Debug, PartialEq, Eq, Clone, Copy)]
327#[brw(repr(u16))]
328pub enum HashAlgorithm {
329 Sha512 = 0x01,
330}
331
332#[smb_message_binrw]
338pub struct PreauthIntegrityCapabilities {
339 #[bw(try_calc(u16::try_from(hash_algorithms.len())))]
340 hash_algorithm_count: u16,
341 #[bw(try_calc(u16::try_from(salt.len())))]
342 salt_length: u16,
343 #[br(count = hash_algorithm_count)]
345 pub hash_algorithms: Vec<HashAlgorithm>,
346 #[br(count = salt_length)]
348 pub salt: Vec<u8>,
349}
350
351#[smb_message_binrw]
357pub struct EncryptionCapabilities {
358 #[bw(try_calc(u16::try_from(ciphers.len())))]
359 cipher_count: u16,
360 #[br(count = cipher_count)]
362 pub ciphers: Vec<EncryptionCipher>,
363}
364
365#[derive(BinRead, BinWrite, Debug, PartialEq, Eq, Clone, Copy)]
369#[brw(repr(u16))]
370pub enum EncryptionCipher {
371 Aes128Ccm = 0x0001,
372 Aes128Gcm = 0x0002,
373 Aes256Ccm = 0x0003,
374 Aes256Gcm = 0x0004,
375}
376
377#[smb_message_binrw]
383#[derive(Clone)]
384pub struct CompressionCapabilities {
385 #[bw(try_calc(u16::try_from(compression_algorithms.len())))]
386 compression_algorithm_count: u16,
387 #[bw(calc = 0)]
388 _padding: u16,
389 pub flags: CompressionCapsFlags,
391 #[br(count = compression_algorithm_count)]
393 pub compression_algorithms: Vec<CompressionAlgorithm>,
394}
395
396#[derive(BinRead, BinWrite, Debug, PartialEq, Eq, Clone, Copy)]
400#[brw(repr(u16))]
401#[repr(u16)]
402pub enum CompressionAlgorithm {
403 None = 0x0000,
404 LZNT1 = 0x0001,
405 LZ77 = 0x0002,
406 LZ77Huffman = 0x0003,
407 PatternV1 = 0x0004,
408 LZ4 = 0x0005,
409}
410
411impl CompressionAlgorithm {
412 pub fn original_size_required(&self) -> bool {
414 matches!(
415 self,
416 CompressionAlgorithm::LZNT1
417 | CompressionAlgorithm::LZ77
418 | CompressionAlgorithm::LZ77Huffman
419 | CompressionAlgorithm::LZ4
420 )
421 }
422}
423
424impl std::fmt::Display for CompressionAlgorithm {
425 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
426 let message_as_string = match self {
427 CompressionAlgorithm::None => "None",
428 CompressionAlgorithm::LZNT1 => "LZNT1",
429 CompressionAlgorithm::LZ77 => "LZ77",
430 CompressionAlgorithm::LZ77Huffman => "LZ77+Huffman",
431 CompressionAlgorithm::PatternV1 => "PatternV1",
432 CompressionAlgorithm::LZ4 => "LZ4",
433 };
434 write!(f, "{} ({:#x})", message_as_string, *self as u16)
435 }
436}
437
438#[smb_dtyp::mbitfield]
444pub struct CompressionCapsFlags {
445 pub chained: bool,
447 #[skip]
448 __: B31,
449}
450
451#[derive(BinRead, BinWrite, Debug, PartialEq, Eq)]
457pub struct NetnameNegotiateContextId {
458 #[br(parse_with = binrw::helpers::until_eof)]
460 pub netname: SizedWideString,
461}
462
463#[smb_dtyp::mbitfield]
469pub struct TransportCapabilities {
470 pub accept_transport_layer_security: bool,
472 #[skip]
473 __: B31,
474}
475
476#[smb_message_binrw]
482pub struct RdmaTransformCapabilities {
483 #[bw(try_calc(u16::try_from(transforms.len())))]
484 transform_count: u16,
485
486 reserved: u16,
487 reserved: u32,
488
489 #[br(count = transform_count)]
491 pub transforms: Vec<RdmaTransformId>,
492}
493
494#[smb_message_binrw]
498#[brw(repr(u16))]
499pub enum RdmaTransformId {
500 None = 0x0000,
501 Encryption = 0x0001,
502 Signing = 0x0002,
503}
504
505#[smb_message_binrw]
511pub struct SigningCapabilities {
512 #[bw(try_calc(u16::try_from(signing_algorithms.len())))]
513 signing_algorithm_count: u16,
514 #[br(count = signing_algorithm_count)]
516 pub signing_algorithms: Vec<SigningAlgorithmId>,
517}
518
519#[derive(BinRead, BinWrite, Debug, PartialEq, Eq, Clone, Copy)]
523#[brw(repr(u16))]
524pub enum SigningAlgorithmId {
525 HmacSha256 = 0x0000,
526 AesCmac = 0x0001,
527 AesGmac = 0x0002,
528}
529
530#[cfg(test)]
531mod tests {
532 use smb_dtyp::make_guid;
533 use smb_tests::hex_to_u8_array;
534 use time::macros::datetime;
535
536 use super::*;
537 use crate::*;
538
539 test_request! {
540 Negotiate {
541 security_mode: NegotiateSecurityMode::new().with_signing_enabled(true),
542 capabilities: GlobalCapabilities::new()
543 .with_dfs(true)
544 .with_leasing(true)
545 .with_large_mtu(true)
546 .with_multi_channel(true)
547 .with_persistent_handles(true)
548 .with_directory_leasing(true)
549 .with_encryption(true)
550 .with_notifications(true),
551 client_guid: make_guid!("{c12e0ddf-43dd-11f0-8b87-000c29801682}"),
552 dialects: vec![
553 Dialect::Smb0202,
554 Dialect::Smb021,
555 Dialect::Smb030,
556 Dialect::Smb0302,
557 Dialect::Smb0311,
558 ],
559 negotiate_context_list: Some(vec![
560 PreauthIntegrityCapabilities {
561 hash_algorithms: vec![HashAlgorithm::Sha512],
562 salt: hex_to_u8_array! {"ed006c304e332890b2bd98617b5ad9ef075994154673696280ffcc0f1291a15d"}
563 }.into(),
564 EncryptionCapabilities { ciphers: vec![
565 EncryptionCipher::Aes128Gcm,
566 EncryptionCipher::Aes128Ccm,
567 EncryptionCipher::Aes256Gcm,
568 EncryptionCipher::Aes256Ccm,
569 ] }.into(),
570 CompressionCapabilities {
571 flags: CompressionCapsFlags::new().with_chained(true),
572 compression_algorithms: vec![
573 CompressionAlgorithm::PatternV1,
574 CompressionAlgorithm::LZ77,
575 CompressionAlgorithm::LZ77Huffman,
576 CompressionAlgorithm::LZNT1,
577 CompressionAlgorithm::LZ4,
578 ]
579 }.into(),
580 SigningCapabilities { signing_algorithms: vec![
581 SigningAlgorithmId::AesGmac,
582 SigningAlgorithmId::AesCmac,
583 SigningAlgorithmId::HmacSha256,
584 ] }.into(),
585 NetnameNegotiateContextId { netname: "localhost".into() }.into(),
586 RdmaTransformCapabilities { transforms: vec![RdmaTransformId::Encryption, RdmaTransformId::Signing] }.into()
587 ])
588 } => "2400050001000000ff000000df0d2ec1dd43f0118b87000c298
589 016827000000006000000020210020003020311030000010026000000
590 0000010020000100ed006c304e332890b2bd98617b5ad9ef075994154
591 673696280ffcc0f1291a15d000002000a000000000004000200010004
592 000300000000000000030012000000000005000000010000000400020
593 003000100050000000000000008000800000000000300020001000000
594 05001200000000006c006f00630061006c0068006f007300740000000
595 000000007000c0000000000020000000000000001000200"
596 }
597
598 test_response! {
599 Negotiate {
600 security_mode: NegotiateSecurityMode::new().with_signing_enabled(true),
601 dialect_revision: NegotiateDialect::Smb0311,
602 server_guid: Guid::from([
603 0xb9, 0x21, 0xf8, 0xe0, 0x15, 0x7, 0xaa, 0x41, 0xbe, 0x38, 0x67, 0xfe, 0xbf,
604 0x5e, 0x2e, 0x11
605 ]),
606 capabilities: GlobalCapabilities::new()
607 .with_dfs(true)
608 .with_leasing(true)
609 .with_large_mtu(true)
610 .with_multi_channel(true)
611 .with_directory_leasing(true),
612 max_transact_size: 8388608,
613 max_read_size: 8388608,
614 max_write_size: 8388608,
615 system_time: datetime!(2025-01-18 16:24:39.448746400).into(),
616 server_start_time: FileTime::default(),
617 buffer: [
618 0x60, 0x28, 0x6, 0x6, 0x2b, 0x6, 0x1, 0x5, 0x5, 0x2, 0xa0, 0x1e, 0x30, 0x1c,
619 0xa0, 0x1a, 0x30, 0x18, 0x6, 0xa, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x82, 0x37, 0x2,
620 0x2, 0x1e, 0x6, 0xa, 0x2b, 0x6, 0x1, 0x4, 0x1, 0x82, 0x37, 0x2, 0x2, 0xa
621 ]
622 .to_vec(),
623 negotiate_context_list: Some(vec![
624 PreauthIntegrityCapabilities {
625 hash_algorithms: vec![HashAlgorithm::Sha512],
626 salt: [
627 0xd5, 0x67, 0x1b, 0x24, 0xa1, 0xe9, 0xcc, 0xc8, 0x93, 0xf5, 0x55,
628 0x5a, 0x31, 0x3, 0x43, 0x5a, 0x85, 0x2b, 0xc3, 0xcb, 0x1a, 0xd3,
629 0x2d, 0xc5, 0x1f, 0x92, 0x80, 0x6e, 0xf3, 0xfb, 0x4d, 0xd4
630 ]
631 .to_vec()
632 }
633 .into(),
634 EncryptionCapabilities {
635 ciphers: vec![EncryptionCipher::Aes128Gcm]
636 }
637 .into(),
638 SigningCapabilities {
639 signing_algorithms: vec![SigningAlgorithmId::AesGmac]
640 }
641 .into(),
642 RdmaTransformCapabilities {
643 transforms: vec![RdmaTransformId::Encryption, RdmaTransformId::Signing]
644 }
645 .into(),
646 CompressionCapabilities {
647 flags: CompressionCapsFlags::new().with_chained(true),
648 compression_algorithms: vec![
649 CompressionAlgorithm::LZ77,
650 CompressionAlgorithm::PatternV1
651 ]
652 }
653 .into(),
654 ])
655 } => "4100010011030500b921f8e01507aa41be3867febf5e2e112f000000000080000000800000008000a876d878c569db01000000000000000080002a00b0000000602806062b0601050502a01e301ca01a3018060a2b06010401823702021e060a2b06010401823702020a0000000000000100260000000000010020000100d5671b24a1e9ccc893f5555a3103435a852bc3cb1ad32dc51f92806ef3fb4dd40000020004000000000001000200000000000800040000000000010002000000000007000c00000000000200000000000000010002000000000003000c0000000000020000000100000002000400"
656 }
657}