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