1use std::io::{Read, Write};
9use std::net::{SocketAddr, TcpStream};
10use std::time::{Duration, Instant, SystemTime};
11
12use super::common::{filetime_to_system_time, map_io_err, system_time_to_us};
13use super::smb_common::build_negotiate_request;
14use crate::time_src::{OffsetMicros, TimeSource, TimeSourceError};
15
16pub struct SmbSource;
17
18struct FieldReader<'a> {
20 buf: &'a [u8],
21 pos: usize,
22}
23
24impl<'a> FieldReader<'a> {
25 fn new(buf: &'a [u8]) -> Self {
26 Self { buf, pos: 0 }
27 }
28
29 fn read_u16_le(&mut self) -> Result<u16, TimeSourceError> {
30 let b = self.next_bytes(2)?;
31 Ok(u16::from_le_bytes([b[0], b[1]]))
32 }
33
34 fn read_u32_le(&mut self) -> Result<u32, TimeSourceError> {
35 let b = self.next_bytes(4)?;
36 Ok(u32::from_le_bytes([b[0], b[1], b[2], b[3]]))
37 }
38
39 fn read_u64_le(&mut self) -> Result<u64, TimeSourceError> {
40 let b = self.next_bytes(8)?;
41 Ok(u64::from_le_bytes([
42 b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
43 ]))
44 }
45
46 fn skip(&mut self, n: usize) -> Result<(), TimeSourceError> {
47 self.next_bytes(n)?;
48 Ok(())
49 }
50
51 fn next_bytes(&mut self, n: usize) -> Result<&'a [u8], TimeSourceError> {
52 let end = self
53 .pos
54 .checked_add(n)
55 .ok_or_else(|| TimeSourceError::Parse("FieldReader overflow".into()))?;
56 if end > self.buf.len() {
57 return Err(TimeSourceError::Parse("SMB body overruns buffer".into()));
58 }
59 let b = &self.buf[self.pos..end];
60 self.pos = end;
61 Ok(b)
62 }
63}
64
65impl TimeSource for SmbSource {
66 fn name(&self) -> &'static str {
67 "smb"
68 }
69
70 fn fetch(
71 &self,
72 target: SocketAddr,
73 timeout: Duration,
74 ) -> Result<OffsetMicros, TimeSourceError> {
75 let smb_addr: SocketAddr = (target.ip(), 445).into();
76 fetch_smb(smb_addr, timeout)
77 }
78}
79
80fn fetch_smb(addr: SocketAddr, timeout: Duration) -> Result<OffsetMicros, TimeSourceError> {
81 let mut stream =
82 TcpStream::connect_timeout(&addr, timeout).map_err(|e| map_io_err(e, "connect"))?;
83 stream
84 .set_read_timeout(Some(timeout))
85 .map_err(|e| TimeSourceError::Protocol(e.to_string()))?;
86 stream
87 .set_write_timeout(Some(timeout))
88 .map_err(|e| TimeSourceError::Protocol(e.to_string()))?;
89
90 let t_send = Instant::now();
91 let t_send_sys = SystemTime::now();
92
93 let request = build_negotiate_request();
94 stream
95 .write_all(&request)
96 .map_err(|e| TimeSourceError::Protocol(e.to_string()))?;
97
98 let mut nb_header = [0u8; 4];
100 stream
101 .read_exact(&mut nb_header)
102 .map_err(|e| map_io_err(e, "read_header"))?;
103 let msg_len = u32::from_be_bytes(nb_header) & 0x00FF_FFFF;
105 if msg_len > 65536 {
106 return Err(TimeSourceError::Protocol(format!(
107 "implausibly large SMB2 response: {} bytes",
108 msg_len
109 )));
110 }
111 if msg_len < 64 + 65 {
112 return Err(TimeSourceError::Parse(format!(
113 "SMB2 response too short: {} bytes",
114 msg_len
115 )));
116 }
117
118 let mut body = vec![0u8; msg_len as usize];
119 stream
120 .read_exact(&mut body)
121 .map_err(|e| map_io_err(e, "read_body"))?;
122
123 let rtt = t_send.elapsed();
124
125 let negotiate = &body[64..];
127 let server_time = parse_negotiate_response(negotiate)?;
128
129 let t_mid_us = system_time_to_us(t_send_sys)? + (rtt.as_micros() as i64) / 2;
132 let server_us = system_time_to_us(server_time)?;
133
134 Ok(server_us - t_mid_us)
135}
136
137fn parse_negotiate_response(b: &[u8]) -> Result<SystemTime, TimeSourceError> {
139 let mut r = FieldReader::new(b);
140 let structure_size = r.read_u16_le()?; if structure_size != 65 {
143 return Err(TimeSourceError::Protocol(format!(
144 "unexpected SMB2 NEGOTIATE_RESPONSE StructureSize: {}",
145 structure_size
146 )));
147 }
148 let _security_mode = r.read_u16_le()?; let _dialect_revision = r.read_u16_le()?; let _negotiate_ctx_cnt = r.read_u16_le()?; r.skip(16)?; let _capabilities = r.read_u32_le()?; let _max_transact = r.read_u32_le()?; let _max_read = r.read_u32_le()?; let _max_write = r.read_u32_le()?; let system_time = r.read_u64_le()?; filetime_to_system_time(system_time)
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164 use std::time::UNIX_EPOCH;
165
166 #[test]
167 fn filetime_unix_epoch() {
168 let ft: u64 = 116_444_736_000_000_000;
170 let st = filetime_to_system_time(ft).unwrap();
171 assert_eq!(st, UNIX_EPOCH);
172 }
173
174 #[test]
175 fn filetime_2024_01_01() {
176 let ft: u64 = 133_485_408_000_000_000;
180 let st = filetime_to_system_time(ft).unwrap();
181 let unix_secs = st.duration_since(UNIX_EPOCH).unwrap().as_secs();
182 assert_eq!(unix_secs, 1_704_067_200);
183 }
184
185 #[test]
186 fn filetime_before_unix_epoch_errors() {
187 assert!(filetime_to_system_time(0).is_err());
188 assert!(filetime_to_system_time(100).is_err());
189 }
190
191 #[test]
192 fn negotiate_response_too_short() {
193 assert!(parse_negotiate_response(&[0u8; 10]).is_err());
194 }
195
196 #[test]
197 fn negotiate_response_bad_structure_size() {
198 let mut b = vec![0u8; 50];
199 b[0..2].copy_from_slice(&99u16.to_le_bytes());
201 assert!(parse_negotiate_response(&b).is_err());
202 }
203
204 #[test]
205 fn build_negotiate_request_has_random_guid() {
206 use crate::protocols::smb_common::build_negotiate_request;
208 let r1 = build_negotiate_request();
209 let r2 = build_negotiate_request();
210 assert_ne!(
211 &r1[80..96],
212 &r2[80..96],
213 "ClientGuid must differ between calls"
214 );
215 assert_ne!(&r1[80..96], &[0u8; 16]);
217 }
218
219 #[test]
220 fn build_negotiate_request_advertises_smb311() {
221 use crate::protocols::smb_common::build_negotiate_request;
222 let req = build_negotiate_request();
223 assert_eq!(
225 u16::from_le_bytes([req[104], req[105]]),
226 0x0311,
227 "first dialect must be SMB 3.1.1"
228 );
229 let neg_ctx_off = u32::from_le_bytes([req[96], req[97], req[98], req[99]]);
231 assert_eq!(
232 neg_ctx_off, 112,
233 "NegotiateContextOffset must be 112 (8-byte aligned from SMB2 header start)"
234 );
235 assert_eq!(
237 u16::from_le_bytes([req[116], req[117]]),
238 0x0001,
239 "negotiate context must be PREAUTH_INTEGRITY_CAPABILITIES"
240 );
241 }
242
243 #[test]
244 fn fetch_smb_rejects_large_msg_len() {
245 let large: u32 = 0x0002_0000; assert!(large > 65536);
250 let nb = [0x00u8, 0x02, 0x00, 0x00];
252 let msg_len = u32::from_be_bytes(nb) & 0x00FF_FFFF;
253 assert_eq!(msg_len, 131072);
254 assert!(msg_len > 65536);
255 }
256
257 use proptest::prelude::*;
258
259 proptest! {
260 #[test]
261 fn parse_negotiate_response_never_panics(data in proptest::collection::vec(any::<u8>(), 0..256)) {
262 let _ = parse_negotiate_response(&data);
263 }
264 }
265}
266
267#[cfg(feature = "fuzzing")]
268pub fn fuzz_parse_negotiate_response(
269 data: &[u8],
270) -> Result<std::time::SystemTime, crate::time_src::TimeSourceError> {
271 parse_negotiate_response(data)
272}