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 #[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#[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 #[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#[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 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}