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