1use std::fmt;
4
5use ldap_client_ber::tag::Tag;
6use ldap_client_ber::{BerReader, BerWriter, Class};
7use zeroize::Zeroizing;
8
9use crate::ProtoError;
10use crate::controls::Control;
11use crate::filter::Filter;
12use crate::result_code::ResultCode;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16pub struct MessageId(pub i32);
17
18#[derive(Debug, Clone)]
20pub struct LdapMessage {
21 pub message_id: MessageId,
22 pub operation: LdapOperation,
23 pub controls: Vec<Control>,
24}
25
26#[derive(Debug, Clone)]
27pub enum LdapOperation {
28 BindRequest(BindRequest),
29 BindResponse(BindResponse),
30 UnbindRequest,
31 SearchRequest(SearchRequest),
32 SearchResultEntry(SearchResultEntry),
33 SearchResultDone(LdapResult),
34 SearchResultReference(Vec<String>),
35 ModifyRequest(ModifyRequest),
36 ModifyResponse(LdapResult),
37 AddRequest(AddRequest),
38 AddResponse(LdapResult),
39 DeleteRequest(String),
40 DeleteResponse(LdapResult),
41 ModifyDnRequest(ModifyDnRequest),
42 ModifyDnResponse(LdapResult),
43 CompareRequest(CompareRequest),
44 CompareResponse(LdapResult),
45 AbandonRequest(MessageId),
46 ExtendedRequest(ExtendedRequest),
47 ExtendedResponse(ExtendedResponse),
48 IntermediateResponse(IntermediateResponse),
49}
50
51const APP_BIND_REQUEST: u32 = 0;
53const APP_BIND_RESPONSE: u32 = 1;
54const APP_UNBIND_REQUEST: u32 = 2;
55const APP_SEARCH_REQUEST: u32 = 3;
56const APP_SEARCH_RESULT_ENTRY: u32 = 4;
57const APP_SEARCH_RESULT_DONE: u32 = 5;
58const APP_MODIFY_REQUEST: u32 = 6;
59const APP_MODIFY_RESPONSE: u32 = 7;
60const APP_ADD_REQUEST: u32 = 8;
61const APP_ADD_RESPONSE: u32 = 9;
62const APP_DEL_REQUEST: u32 = 10;
63const APP_DEL_RESPONSE: u32 = 11;
64const APP_MODIFY_DN_REQUEST: u32 = 12;
65const APP_MODIFY_DN_RESPONSE: u32 = 13;
66const APP_COMPARE_REQUEST: u32 = 14;
67const APP_COMPARE_RESPONSE: u32 = 15;
68const APP_ABANDON_REQUEST: u32 = 16;
69const APP_SEARCH_RESULT_REFERENCE: u32 = 19;
70const APP_EXTENDED_REQUEST: u32 = 23;
71const APP_EXTENDED_RESPONSE: u32 = 24;
72const APP_INTERMEDIATE_RESPONSE: u32 = 25;
73
74#[derive(Debug, Clone)]
77pub struct BindRequest {
78 pub version: i64,
79 pub name: String,
80 pub authentication: BindAuthentication,
81}
82
83#[derive(Clone)]
84pub enum BindAuthentication {
85 Simple(Zeroizing<Vec<u8>>),
86 Sasl {
87 mechanism: String,
88 credentials: Option<Zeroizing<Vec<u8>>>,
89 },
90}
91
92impl fmt::Debug for BindAuthentication {
93 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94 match self {
95 Self::Simple(_) => f.debug_tuple("Simple").field(&"[REDACTED]").finish(),
96 Self::Sasl { mechanism, .. } => f
97 .debug_struct("Sasl")
98 .field("mechanism", mechanism)
99 .field("credentials", &"[REDACTED]")
100 .finish(),
101 }
102 }
103}
104
105#[derive(Debug, Clone)]
106pub struct BindResponse {
107 pub result: LdapResult,
108 pub server_sasl_creds: Option<Vec<u8>>,
109}
110
111#[derive(Debug, Clone, PartialEq, Eq)]
112pub struct LdapResult {
113 pub code: ResultCode,
114 pub matched_dn: String,
115 pub diagnostic_message: String,
116 pub referral: Vec<String>,
117}
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq)]
120pub enum SearchScope {
121 BaseObject = 0,
122 SingleLevel = 1,
123 WholeSubtree = 2,
124}
125
126#[derive(Debug, Clone, Copy, PartialEq, Eq)]
127pub enum DerefAliases {
128 NeverDerefAliases = 0,
129 DerefInSearching = 1,
130 DerefFindingBaseObj = 2,
131 DerefAlways = 3,
132}
133
134#[derive(Debug, Clone)]
135pub struct SearchRequest {
136 pub base_dn: String,
137 pub scope: SearchScope,
138 pub deref_aliases: DerefAliases,
139 pub size_limit: i32,
140 pub time_limit: i32,
141 pub types_only: bool,
142 pub filter: Filter,
143 pub attributes: Vec<String>,
144}
145
146#[derive(Debug, Clone, PartialEq, Eq)]
147pub struct SearchResultEntry {
148 pub dn: String,
149 pub attributes: Vec<PartialAttribute>,
150}
151
152#[derive(Debug, Clone, PartialEq, Eq)]
153pub struct PartialAttribute {
154 pub name: String,
155 pub values: Vec<Vec<u8>>,
156}
157
158impl PartialAttribute {
159 pub fn string_values(&self) -> Vec<&str> {
160 self.values
161 .iter()
162 .filter_map(|v| std::str::from_utf8(v).ok())
163 .collect()
164 }
165
166 pub fn first_string_value(&self) -> Option<&str> {
167 self.values
168 .first()
169 .and_then(|v| std::str::from_utf8(v).ok())
170 }
171
172 pub fn first_value(&self) -> Option<&[u8]> {
173 self.values.first().map(|v| v.as_slice())
174 }
175}
176
177impl SearchResultEntry {
178 pub fn attr(&self, name: &str) -> Option<&PartialAttribute> {
179 self.attributes
180 .iter()
181 .find(|a| a.name.eq_ignore_ascii_case(name))
182 }
183
184 pub fn first_value(&self, attr: &str) -> Option<&[u8]> {
185 self.attr(attr).and_then(|a| a.first_value())
186 }
187
188 pub fn first_string_value(&self, attr: &str) -> Option<&str> {
189 self.attr(attr).and_then(|a| a.first_string_value())
190 }
191}
192
193#[derive(Debug, Clone)]
194pub struct CompareRequest {
195 pub dn: String,
196 pub attr: String,
197 pub value: Vec<u8>,
198}
199
200#[derive(Debug, Clone)]
201pub struct AddRequest {
202 pub dn: String,
203 pub attributes: Vec<PartialAttribute>,
204}
205
206#[derive(Debug, Clone)]
207pub struct ModifyRequest {
208 pub dn: String,
209 pub changes: Vec<Modification>,
210}
211
212#[derive(Debug, Clone)]
213pub struct Modification {
214 pub operation: ModifyOperation,
215 pub attribute: PartialAttribute,
216}
217
218#[derive(Debug, Clone, Copy, PartialEq, Eq)]
219pub enum ModifyOperation {
220 Add = 0,
221 Delete = 1,
222 Replace = 2,
223 Increment = 3,
224}
225
226#[derive(Debug, Clone)]
227pub struct ModifyDnRequest {
228 pub dn: String,
229 pub new_rdn: String,
230 pub delete_old_rdn: bool,
231 pub new_superior: Option<String>,
232}
233
234#[derive(Debug, Clone)]
235pub struct ExtendedRequest {
236 pub oid: String,
237 pub value: Option<Vec<u8>>,
238}
239
240#[derive(Debug, Clone)]
241pub struct ExtendedResponse {
242 pub result: LdapResult,
243 pub oid: Option<String>,
244 pub value: Option<Vec<u8>>,
245}
246
247#[derive(Debug, Clone)]
248pub struct IntermediateResponse {
249 pub oid: Option<String>,
250 pub value: Option<Vec<u8>>,
251}
252
253pub trait HasLdapResult {
254 fn result(&self) -> &LdapResult;
255
256 fn is_success(&self) -> bool {
257 self.result().code.is_success()
258 }
259
260 fn is_referral(&self) -> bool {
261 self.result().code.is_referral()
262 }
263
264 fn referral_urls(&self) -> &[String] {
265 &self.result().referral
266 }
267}
268
269impl HasLdapResult for LdapResult {
270 fn result(&self) -> &LdapResult {
271 self
272 }
273}
274
275impl HasLdapResult for BindResponse {
276 fn result(&self) -> &LdapResult {
277 &self.result
278 }
279}
280
281impl HasLdapResult for ExtendedResponse {
282 fn result(&self) -> &LdapResult {
283 &self.result
284 }
285}
286
287impl LdapMessage {
290 pub fn encode(&self) -> Vec<u8> {
291 let mut w = BerWriter::new();
292 w.write_sequence(Tag::sequence(), |msg| {
293 msg.write_integer(self.message_id.0 as i64);
294 encode_operation(msg, &self.operation);
295 if !self.controls.is_empty() {
296 crate::controls::encode_controls(msg, &self.controls);
297 }
298 });
299 w.into_bytes()
300 }
301
302 pub fn decode(data: &[u8]) -> Result<Self, ProtoError> {
303 let mut r = BerReader::new(data);
304 let (message_id, op_tag, op_value, controls) =
305 r.read_sequence_lax(Tag::sequence(), |msg| {
306 let raw_id = msg.read_integer()?;
307 if raw_id < 0 || raw_id > i32::MAX as i64 {
308 return Err(ldap_client_ber::BerError::InvalidInteger);
309 }
310 let message_id = MessageId(raw_id as i32);
311 let (tag, value) = msg.read_element()?;
312 let op_value = value.to_vec();
313
314 let mut controls = Vec::new();
315 if !msg.is_empty() {
316 let peek = msg.peek_tag()?;
317 if peek.class == Class::Context && peek.number == 0 && peek.constructed {
318 controls = crate::controls::decode_controls(msg)?;
319 }
320 }
321
322 Ok((message_id, tag, op_value, controls))
323 })?;
324
325 let operation = decode_operation(op_tag, &op_value)?;
326
327 Ok(LdapMessage {
328 message_id,
329 operation,
330 controls,
331 })
332 }
333}
334
335fn encode_operation(w: &mut BerWriter, op: &LdapOperation) {
336 match op {
337 LdapOperation::BindRequest(req) => {
338 w.write_sequence(Tag::application(APP_BIND_REQUEST), |inner| {
339 inner.write_integer(req.version);
340 inner.write_bytes(req.name.as_bytes());
341 match &req.authentication {
342 BindAuthentication::Simple(pw) => {
343 inner.write_octet_string(Tag::context(0), pw);
344 }
345 BindAuthentication::Sasl {
346 mechanism,
347 credentials,
348 } => {
349 inner.write_sequence(Tag::context_constructed(3), |sasl| {
350 sasl.write_bytes(mechanism.as_bytes());
351 if let Some(creds) = credentials {
352 sasl.write_bytes(creds);
353 }
354 });
355 }
356 }
357 });
358 }
359 LdapOperation::UnbindRequest => {
360 w.write_octet_string(Tag::application_primitive(APP_UNBIND_REQUEST), &[]);
362 }
363 LdapOperation::SearchRequest(req) => {
364 w.write_sequence(Tag::application(APP_SEARCH_REQUEST), |inner| {
365 inner.write_bytes(req.base_dn.as_bytes());
366 inner.write_enumerated(req.scope as i64);
367 inner.write_enumerated(req.deref_aliases as i64);
368 inner.write_integer(req.size_limit as i64);
369 inner.write_integer(req.time_limit as i64);
370 inner.write_boolean(req.types_only);
371 req.filter.encode(inner);
372 inner.write_sequence(Tag::sequence(), |attrs| {
373 for attr in &req.attributes {
374 attrs.write_bytes(attr.as_bytes());
375 }
376 });
377 });
378 }
379 LdapOperation::CompareRequest(req) => {
380 w.write_sequence(Tag::application(APP_COMPARE_REQUEST), |inner| {
381 inner.write_bytes(req.dn.as_bytes());
382 inner.write_sequence(Tag::sequence(), |ava| {
383 ava.write_bytes(req.attr.as_bytes());
384 ava.write_bytes(&req.value);
385 });
386 });
387 }
388 LdapOperation::AddRequest(req) => {
389 w.write_sequence(Tag::application(APP_ADD_REQUEST), |inner| {
390 inner.write_bytes(req.dn.as_bytes());
391 inner.write_sequence(Tag::sequence(), |attrs| {
392 for attr in &req.attributes {
393 encode_partial_attribute(attrs, attr);
394 }
395 });
396 });
397 }
398 LdapOperation::DeleteRequest(dn) => {
399 w.write_octet_string(Tag::application_primitive(APP_DEL_REQUEST), dn.as_bytes());
401 }
402 LdapOperation::ModifyRequest(req) => {
403 w.write_sequence(Tag::application(APP_MODIFY_REQUEST), |inner| {
404 inner.write_bytes(req.dn.as_bytes());
405 inner.write_sequence(Tag::sequence(), |changes| {
406 for modification in &req.changes {
407 changes.write_sequence(Tag::sequence(), |change| {
408 change.write_enumerated(modification.operation as i64);
409 encode_partial_attribute(change, &modification.attribute);
410 });
411 }
412 });
413 });
414 }
415 LdapOperation::ModifyDnRequest(req) => {
416 w.write_sequence(Tag::application(APP_MODIFY_DN_REQUEST), |inner| {
417 inner.write_bytes(req.dn.as_bytes());
418 inner.write_bytes(req.new_rdn.as_bytes());
419 inner.write_boolean(req.delete_old_rdn);
420 if let Some(sup) = &req.new_superior {
421 inner.write_octet_string(Tag::context(0), sup.as_bytes());
422 }
423 });
424 }
425 LdapOperation::AbandonRequest(id) => {
426 let bytes = ldap_client_ber::writer::encode_i64_bytes(id.0 as i64);
428 w.write_octet_string(Tag::application_primitive(APP_ABANDON_REQUEST), &bytes);
429 }
430 LdapOperation::ExtendedRequest(req) => {
431 w.write_sequence(Tag::application(APP_EXTENDED_REQUEST), |inner| {
432 inner.write_octet_string(Tag::context(0), req.oid.as_bytes());
433 if let Some(val) = &req.value {
434 inner.write_octet_string(Tag::context(1), val);
435 }
436 });
437 }
438 _ => unreachable!("attempted to encode a response operation"),
439 }
440}
441
442fn encode_partial_attribute(w: &mut BerWriter, attr: &PartialAttribute) {
443 w.write_sequence(Tag::sequence(), |inner| {
444 inner.write_bytes(attr.name.as_bytes());
445 inner.write_sequence(Tag::set(), |vals| {
446 for val in &attr.values {
447 vals.write_bytes(val);
448 }
449 });
450 });
451}
452
453fn to_utf8(bytes: &[u8]) -> Result<String, ldap_client_ber::BerError> {
456 std::str::from_utf8(bytes)
457 .map(|s| s.to_owned())
458 .map_err(|_| ldap_client_ber::BerError::InvalidUtf8)
459}
460
461fn decode_operation(tag: Tag, value: &[u8]) -> Result<LdapOperation, ProtoError> {
462 if tag.class != Class::Application {
463 return Err(ProtoError::Protocol(format!(
464 "expected APPLICATION tag, got {tag:?}"
465 )));
466 }
467
468 let mut r = BerReader::new(value);
469
470 match tag.number {
471 APP_BIND_RESPONSE => {
472 let result = decode_ldap_result(&mut r)?;
473 let server_sasl_creds = if !r.is_empty() {
474 let tag = r.peek_tag()?;
475 if tag.class == Class::Context && tag.number == 7 {
476 Some(r.read_tagged_implicit_octet_string(7)?.to_vec())
477 } else {
478 None
479 }
480 } else {
481 None
482 };
483 Ok(LdapOperation::BindResponse(BindResponse {
484 result,
485 server_sasl_creds,
486 }))
487 }
488 APP_SEARCH_RESULT_ENTRY => {
489 let dn = to_utf8(r.read_octet_string()?)?;
490 let mut attributes = Vec::new();
491 r.read_sequence(Tag::sequence(), |attrs| {
492 while !attrs.is_empty() {
493 attrs.read_sequence(Tag::sequence(), |attr| {
494 let name = to_utf8(attr.read_octet_string()?)?;
495 let mut values = Vec::new();
496 attr.read_sequence(Tag::set(), |vals| {
497 while !vals.is_empty() {
498 values.push(vals.read_octet_string()?.to_vec());
499 }
500 Ok(())
501 })?;
502 attributes.push(PartialAttribute { name, values });
503 Ok(())
504 })?;
505 }
506 Ok(())
507 })?;
508 Ok(LdapOperation::SearchResultEntry(SearchResultEntry {
509 dn,
510 attributes,
511 }))
512 }
513 APP_SEARCH_RESULT_DONE => {
514 let result = decode_ldap_result(&mut r)?;
515 Ok(LdapOperation::SearchResultDone(result))
516 }
517 APP_SEARCH_RESULT_REFERENCE => {
518 let mut urls = Vec::new();
519 while !r.is_empty() {
520 urls.push(to_utf8(r.read_octet_string()?)?);
521 }
522 Ok(LdapOperation::SearchResultReference(urls))
523 }
524 APP_MODIFY_RESPONSE => {
525 let result = decode_ldap_result(&mut r)?;
526 Ok(LdapOperation::ModifyResponse(result))
527 }
528 APP_ADD_RESPONSE => {
529 let result = decode_ldap_result(&mut r)?;
530 Ok(LdapOperation::AddResponse(result))
531 }
532 APP_DEL_RESPONSE => {
533 let result = decode_ldap_result(&mut r)?;
534 Ok(LdapOperation::DeleteResponse(result))
535 }
536 APP_MODIFY_DN_RESPONSE => {
537 let result = decode_ldap_result(&mut r)?;
538 Ok(LdapOperation::ModifyDnResponse(result))
539 }
540 APP_COMPARE_RESPONSE => {
541 let result = decode_ldap_result(&mut r)?;
542 Ok(LdapOperation::CompareResponse(result))
543 }
544 APP_EXTENDED_RESPONSE => {
545 let result = decode_ldap_result(&mut r)?;
546 let mut oid = None;
547 let mut ext_value = None;
548 while !r.is_empty() {
549 let tag = r.peek_tag()?;
550 match (tag.class, tag.number) {
551 (Class::Context, 10) => {
552 oid = Some(to_utf8(r.read_tagged_implicit_octet_string(10)?)?);
553 }
554 (Class::Context, 11) => {
555 ext_value = Some(r.read_tagged_implicit_octet_string(11)?.to_vec());
556 }
557 _ => {
558 r.read_element()?;
559 }
560 }
561 }
562 Ok(LdapOperation::ExtendedResponse(ExtendedResponse {
563 result,
564 oid,
565 value: ext_value,
566 }))
567 }
568 APP_INTERMEDIATE_RESPONSE => {
569 let mut oid = None;
570 let mut value = None;
571 while !r.is_empty() {
572 let tag = r.peek_tag()?;
573 match (tag.class, tag.number) {
574 (Class::Context, 0) => {
575 oid = Some(to_utf8(r.read_tagged_implicit_octet_string(0)?)?);
576 }
577 (Class::Context, 1) => {
578 value = Some(r.read_tagged_implicit_octet_string(1)?.to_vec());
579 }
580 _ => {
581 r.read_element()?;
582 }
583 }
584 }
585 Ok(LdapOperation::IntermediateResponse(IntermediateResponse {
586 oid,
587 value,
588 }))
589 }
590 n => Err(ProtoError::Protocol(format!(
591 "unknown application tag: {n}"
592 ))),
593 }
594}
595
596fn decode_ldap_result(r: &mut BerReader<'_>) -> Result<LdapResult, ProtoError> {
597 let code = ResultCode::from_i64(r.read_enumerated()?);
598 let matched_dn = to_utf8(r.read_octet_string()?)?;
599 let diagnostic_message = String::from_utf8_lossy(r.read_octet_string()?).into_owned();
601
602 let mut referral = Vec::new();
603 if !r.is_empty() {
604 let tag = r.peek_tag()?;
605 if tag.class == Class::Context && tag.number == 3 {
606 r.read_sequence(Tag::context_constructed(3), |inner| {
607 while !inner.is_empty() {
608 referral.push(to_utf8(inner.read_octet_string()?)?);
609 }
610 Ok(())
611 })?;
612 }
613 }
614
615 Ok(LdapResult {
616 code,
617 matched_dn,
618 diagnostic_message,
619 referral,
620 })
621}