ad_time/protocols/
cldap.rs1use std::net::{SocketAddr, UdpSocket};
23use std::time::{Duration, Instant, SystemTime};
24
25use rand::seq::SliceRandom;
26use rand::Rng;
27
28use super::ber::{encode_integer_i32, encode_tlv};
29use super::common::{map_io_err, parse_generalized_time, system_time_to_us};
30use super::socket_opts::set_windows_ttl_udp;
31use crate::time_src::{OffsetMicros, TimeSource, TimeSourceError};
32
33pub struct CldapSource;
34
35impl TimeSource for CldapSource {
36 fn name(&self) -> &'static str {
37 "cldap"
38 }
39
40 fn fetch(
41 &self,
42 target: SocketAddr,
43 timeout: Duration,
44 ) -> Result<OffsetMicros, TimeSourceError> {
45 let addr: SocketAddr = (target.ip(), 389).into();
46 fetch_cldap(addr, timeout)
47 }
48}
49
50fn fetch_cldap(addr: SocketAddr, timeout: Duration) -> Result<OffsetMicros, TimeSourceError> {
51 let socket = UdpSocket::bind("0.0.0.0:0").map_err(|e| map_io_err(e, "bind"))?;
52 set_windows_ttl_udp(&socket).map_err(|e| TimeSourceError::Protocol(e.to_string()))?;
53 socket
54 .set_read_timeout(Some(timeout))
55 .map_err(|e| map_io_err(e, "set_read_timeout"))?;
56 socket
57 .set_write_timeout(Some(timeout))
58 .map_err(|e| map_io_err(e, "set_write_timeout"))?;
59
60 let msg_id = rand::rng().random_range(1..=1000);
62
63 let req = build_cldap_search_request(msg_id);
64
65 let t_send = Instant::now();
66 let t_send_sys = SystemTime::now();
67
68 socket
69 .send_to(&req, addr)
70 .map_err(|e| map_io_err(e, "send_to"))?;
71
72 let deadline = Instant::now() + timeout;
76 let mut buf = [0u8; 4096];
77 let len = loop {
78 let remaining = deadline
79 .checked_duration_since(Instant::now())
80 .filter(|d| !d.is_zero())
81 .ok_or(TimeSourceError::Timeout)?;
82 socket
83 .set_read_timeout(Some(remaining))
84 .map_err(|e| map_io_err(e, "set_read_timeout"))?;
85
86 let (len, src) = socket
87 .recv_from(&mut buf)
88 .map_err(|e| map_io_err(e, "recv_from"))?;
89 if src.ip() == addr.ip() {
90 break len;
91 }
92 };
93
94 let rtt = t_send.elapsed();
95 let resp = &buf[..len];
96
97 let server_time = parse_cldap_search_response(resp, msg_id)?;
98
99 let t_mid_us = system_time_to_us(t_send_sys)? + (rtt.as_micros() as i64) / 2;
100 let server_us = system_time_to_us(server_time)?;
101
102 Ok(server_us - t_mid_us)
103}
104
105fn build_cldap_search_request(msg_id: i32) -> Vec<u8> {
106 let time_limit_enc = encode_integer_i32(0);
110
111 let base_object = encode_tlv(0x04, b""); let scope = encode_tlv(0x0a, &[0]); let deref = encode_tlv(0x0a, &[0]); let size_limit = encode_integer_i32(1); let types_only = encode_tlv(0x01, &[0x00]); let filter = encode_tlv(0x87, b"objectClass");
120
121 let mut attrs = vec![
123 "schemaNamingContext",
124 "namingContexts",
125 "currentTime",
126 "dnsHostName",
127 "supportedLDAPVersion",
128 ];
129 attrs.shuffle(&mut rand::rng());
130 let mut attrs_seq = Vec::new();
131 for a in attrs {
132 attrs_seq.extend_from_slice(&encode_tlv(0x04, a.as_bytes()));
133 }
134 let attributes = encode_tlv(0x30, &attrs_seq); let mut search_req_seq = Vec::new();
137 search_req_seq.extend_from_slice(&base_object);
138 search_req_seq.extend_from_slice(&scope);
139 search_req_seq.extend_from_slice(&deref);
140 search_req_seq.extend_from_slice(&size_limit);
141 search_req_seq.extend_from_slice(&time_limit_enc);
142 search_req_seq.extend_from_slice(&types_only);
143 search_req_seq.extend_from_slice(&filter);
144 search_req_seq.extend_from_slice(&attributes);
145
146 let protocol_op = encode_tlv(0x63, &search_req_seq); let mut ldap_msg_seq = Vec::new();
149 ldap_msg_seq.extend_from_slice(&encode_integer_i32(msg_id));
150 ldap_msg_seq.extend_from_slice(&protocol_op);
151
152 encode_tlv(0x30, &ldap_msg_seq) }
154
155struct BerReader<'a> {
157 buf: &'a [u8],
158 pos: usize,
159}
160
161impl<'a> BerReader<'a> {
162 fn new(buf: &'a [u8]) -> Self {
163 Self { buf, pos: 0 }
164 }
165
166 fn read_tlv(&mut self) -> Result<(u8, &'a [u8]), TimeSourceError> {
167 if self.pos >= self.buf.len() {
168 return Err(TimeSourceError::Parse("Unexpected EOF in BER".into()));
169 }
170 let tag = self.buf[self.pos];
171 self.pos += 1;
172
173 if self.pos >= self.buf.len() {
174 return Err(TimeSourceError::Parse(
175 "Unexpected EOF reading BER length".into(),
176 ));
177 }
178 let mut len = self.buf[self.pos] as usize;
179 self.pos += 1;
180
181 if len & 0x80 != 0 {
182 let len_bytes = len & 0x7F;
183 let end_bytes = self
184 .pos
185 .checked_add(len_bytes)
186 .ok_or_else(|| TimeSourceError::Parse("BER length overflow".into()))?;
187 if len_bytes == 0 || end_bytes > self.buf.len() {
188 return Err(TimeSourceError::Parse(
189 "Invalid BER long form length".into(),
190 ));
191 }
192 let mut actual_len = 0;
193 for i in 0..len_bytes {
194 actual_len = (actual_len << 8) | (self.buf[self.pos + i] as usize);
195 }
196 self.pos += len_bytes;
197 len = actual_len;
198 }
199
200 let end_pos = self
201 .pos
202 .checked_add(len)
203 .ok_or_else(|| TimeSourceError::Parse("BER value length overflow".into()))?;
204 if end_pos > self.buf.len() {
205 return Err(TimeSourceError::Parse(
206 "BER value length exceeds buffer".into(),
207 ));
208 }
209
210 let val = &self.buf[self.pos..end_pos];
211 self.pos = end_pos;
212
213 Ok((tag, val))
214 }
215
216 fn has_more(&self) -> bool {
217 self.pos < self.buf.len()
218 }
219}
220
221fn parse_cldap_search_response(
222 resp: &[u8],
223 expected_msg_id: i32,
224) -> Result<SystemTime, TimeSourceError> {
225 let mut msg_reader = BerReader::new(resp);
226 let (tag, msg_val) = msg_reader.read_tlv()?;
227 if tag != 0x30 {
228 return Err(TimeSourceError::Parse(
229 "Expected LDAPMessage SEQUENCE".into(),
230 ));
231 }
232
233 let mut inner = BerReader::new(msg_val);
234
235 let (id_tag, id_val) = inner.read_tlv()?;
237 if id_tag != 0x02 {
238 return Err(TimeSourceError::Parse("Expected messageID INTEGER".into()));
239 }
240 if id_val.len() > 4 {
241 return Err(TimeSourceError::Parse("messageID too long".into()));
242 }
243 let mut msg_id = 0;
244 for &b in id_val {
245 msg_id = (msg_id << 8) | (b as i32);
246 }
247 if msg_id != expected_msg_id {
248 return Err(TimeSourceError::Protocol("Message ID mismatch".into()));
249 }
250
251 let (op_tag, op_val) = inner.read_tlv()?;
253 if op_tag != 0x64 {
254 return Err(TimeSourceError::Protocol(format!(
256 "Expected SearchResEntry (0x64), got 0x{:02X}",
257 op_tag
258 )));
259 }
260
261 let mut entry = BerReader::new(op_val);
262 let (_dn_tag, _dn_val) = entry.read_tlv()?; let (attr_tag, attr_val) = entry.read_tlv()?; if attr_tag != 0x30 {
266 return Err(TimeSourceError::Parse(
267 "Expected attributes SEQUENCE".into(),
268 ));
269 }
270
271 let mut attrs = BerReader::new(attr_val);
272 while attrs.has_more() {
273 let (seq_tag, seq_val) = attrs.read_tlv()?;
274 if seq_tag != 0x30 {
275 continue;
276 }
277
278 let mut attr = BerReader::new(seq_val);
279 let (type_tag, type_val) = attr.read_tlv()?;
280 if type_tag != 0x04 {
281 continue;
282 } if type_val == b"currentTime" {
285 let (set_tag, set_val) = attr.read_tlv()?;
286 if set_tag != 0x31 {
287 return Err(TimeSourceError::Parse(
289 "Expected SET OF for attribute values".into(),
290 ));
291 }
292
293 let mut vals = BerReader::new(set_val);
294 let (v_tag, v_val) = vals.read_tlv()?;
295 if v_tag != 0x04 {
296 return Err(TimeSourceError::Parse(
297 "Expected OCTET STRING for currentTime".into(),
298 ));
299 }
300
301 let time_str = std::str::from_utf8(v_val)
302 .map_err(|_| TimeSourceError::Parse("currentTime is not valid UTF-8".into()))?;
303
304 return parse_generalized_time(time_str);
305 }
306 }
307
308 Err(TimeSourceError::Parse(
309 "currentTime attribute not found in CLDAP response".into(),
310 ))
311}
312
313#[cfg(test)]
314mod tests {
315 use super::*;
316 use std::time::UNIX_EPOCH;
317
318 #[test]
319 fn parse_generalized_time_works() {
320 let t1 = parse_generalized_time("20240115000000.0Z").unwrap();
322 let t2 = parse_generalized_time("20240115000000Z").unwrap();
323 assert_eq!(t1, t2);
324
325 let d = t1.duration_since(UNIX_EPOCH).unwrap().as_secs();
326 assert_eq!(d, 1_705_276_800);
328 }
329
330 #[test]
331 fn build_cldap_search_request_structure() {
332 let req = build_cldap_search_request(123);
333 assert_eq!(req[0], 0x30);
335 }
336
337 use proptest::prelude::*;
338
339 proptest! {
340 #[test]
341 fn parse_cldap_search_response_never_panics(
342 data in proptest::collection::vec(any::<u8>(), 0..512),
343 ) {
344 let _ = parse_cldap_search_response(&data, 1);
345 }
346 }
347}
348
349#[cfg(feature = "fuzzing")]
350pub fn fuzz_parse_cldap_response(
351 resp: &[u8],
352 msg_id: i32,
353) -> Result<std::time::SystemTime, crate::time_src::TimeSourceError> {
354 parse_cldap_search_response(resp, msg_id)
355}