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