1extern crate libc;
7use libc::{c_int, c_char, c_void, timeval};
8use std::collections::HashMap;
9use std::ffi::{CStr, CString};
10use std::ptr;
11use std::slice;
12use std::boxed;
13use std::ptr::null_mut;
14
15pub mod codes;
16pub mod errors;
17
18#[repr(C)]
19struct LDAP;
20
21#[repr(C)]
22struct LDAPMessage;
23
24#[repr(C)]
25pub struct LDAPControl;
26
27#[repr(C)]
28struct BerElement;
29
30unsafe impl Sync for LDAP {}
31unsafe impl Send for LDAP {}
32
33#[link(name = "lber")]
34#[allow(improper_ctypes)]
35extern "C" {
36 fn ber_free(ber: *const BerElement, freebuf: c_int);
37}
38
39#[link(name = "ldap_r")]
40#[allow(improper_ctypes)]
41extern "C" {
42 static ber_pvt_opt_on: c_char;
43 fn ldap_initialize(ldap: *mut *mut LDAP, uri: *const c_char) -> c_int;
44 fn ldap_memfree(p: *mut c_void);
45 fn ldap_msgfree(msg: *mut LDAPMessage) -> c_int;
46 fn ldap_err2string(err: c_int) -> *const c_char;
47 fn ldap_first_entry(ldap: *mut LDAP, result: *mut LDAPMessage) -> *mut LDAPMessage;
48 fn ldap_next_entry(ldap: *mut LDAP, entry: *mut LDAPMessage) -> *mut LDAPMessage;
49 fn ldap_get_dn(ldap: *mut LDAP, entry: *mut LDAPMessage) -> *const c_char;
50 fn ldap_get_values(ldap: *mut LDAP,
51 entry: *mut LDAPMessage,
52 attr: *const c_char)
53 -> *const *const c_char;
54 fn ldap_count_values(vals: *const *const c_char) -> c_int;
55 fn ldap_value_free(vals: *const *const c_char);
56 fn ldap_set_option(ldap: *const LDAP, option: c_int, invalue: *const c_void) -> c_int;
57 fn ldap_simple_bind_s(ldap: *mut LDAP, who: *const c_char, pass: *const c_char) -> c_int;
58 fn ldap_first_attribute(ldap: *mut LDAP,
59 entry: *mut LDAPMessage,
60 berptr: *mut *mut BerElement)
61 -> *const c_char;
62 fn ldap_next_attribute(ldap: *mut LDAP,
63 entry: *mut LDAPMessage,
64 berptr: *mut BerElement)
65 -> *const c_char;
66 fn ldap_search_ext_s(ldap: *mut LDAP,
67 base: *const c_char,
68 scope: c_int,
69 filter: *const c_char,
70 attrs: *const *const c_char,
71 attrsonly: c_int,
72 serverctrls: *mut *mut LDAPControl,
73 clientctrls: *mut *mut LDAPControl,
74 timeout: *mut timeval,
75 sizelimit: c_int,
76 res: *mut *mut LDAPMessage)
77 -> c_int;
78 fn ldap_unbind_ext_s(ldap: *mut LDAP,
79 sctrls: *mut *mut LDAPControl,
80 cctrls: *mut *mut LDAPControl)
81 -> c_int;
82 fn ldap_start_tls_s(ldap: *mut LDAP,
83 scrtrls: *mut *mut LDAPControl,
84 cctrls: *mut *mut LDAPControl) -> c_int;
85}
86
87pub type LDAPResponse = Vec<HashMap<String, Vec<String>>>;
93
94
95pub struct RustLDAP {
104 ldap_ptr: *mut LDAP,
106}
107
108
109unsafe impl Sync for RustLDAP {}
110unsafe impl Send for RustLDAP {}
111
112impl Drop for RustLDAP {
113 fn drop(&mut self) {
114 let rc = unsafe { ldap_unbind_ext_s(self.ldap_ptr, ptr::null_mut(), ptr::null_mut()) };
116
117 if rc != codes::results::LDAP_SUCCESS {
119 unsafe {
120 let raw_estr = ldap_err2string(rc as c_int);
122 panic!(CStr::from_ptr(raw_estr).to_owned().into_string().unwrap());
123 }
124 }
125 }
126}
127
128pub trait LDAPOptionValue {
142 fn as_cvoid_ptr(&self) -> *const c_void;
143}
144
145impl LDAPOptionValue for str {
146 fn as_cvoid_ptr(&self) -> *const c_void {
147 let string = CString::new(self).unwrap();
148 string.into_raw() as *const c_void
149 }
150}
151
152impl LDAPOptionValue for i32 {
153 fn as_cvoid_ptr(&self) -> *const c_void {
154 let mem = boxed::Box::new(*self);
155 boxed::Box::into_raw(mem) as *const c_void
156 }
157}
158
159impl LDAPOptionValue for bool {
160 fn as_cvoid_ptr(&self) -> *const c_void {
161 if *self {
162 let mem = unsafe { boxed::Box::new(&ber_pvt_opt_on) };
163 boxed::Box::into_raw(mem) as *const c_void
164 } else {
165 let mem = boxed::Box::new(0);
166 boxed::Box::into_raw(mem) as *const c_void
167 }
168 }
169}
170
171impl RustLDAP {
172 pub fn new(uri: &str) -> Result<RustLDAP, errors::LDAPError> {
184
185 let mut cldap = ptr::null_mut();
187
188 let uri_cstring = CString::new(uri).unwrap();
189
190 unsafe {
191 let res = ldap_initialize(&mut cldap, uri_cstring.as_ptr());
192 if res != codes::results::LDAP_SUCCESS {
193 let raw_estr = ldap_err2string(res as c_int);
194 return Err(errors::LDAPError::NativeError(CStr::from_ptr(raw_estr)
195 .to_owned()
196 .into_string()
197 .unwrap()));
198 }
199
200 }
201
202 Ok(RustLDAP { ldap_ptr: cldap })
203 }
204
205 pub fn set_option<T: LDAPOptionValue + ?Sized>(&self, option: i32, value: &T) -> bool {
216 let ptr: *const c_void = value.as_cvoid_ptr();
217 unsafe {
218 let res: i32;
219 res = ldap_set_option(self.ldap_ptr, option, ptr);
220 let _ = boxed::Box::from_raw(ptr as *mut c_void);
222 res == 0
223 }
224 }
225
226 pub fn simple_bind(&self, who: &str, pass: &str) -> Result<i32, errors::LDAPError> {
239 let who_cstr = CString::new(who).unwrap();
240 let pass_cstr = CString::new(pass).unwrap();
241 let who_ptr = who_cstr.as_ptr();
242 let pass_ptr = pass_cstr.as_ptr();
243 unsafe {
244 let res = ldap_simple_bind_s(self.ldap_ptr, who_ptr, pass_ptr);
245 if res < 0 {
246 let raw_estr = ldap_err2string(res as c_int);
247 return Err(errors::LDAPError::NativeError(CStr::from_ptr(raw_estr)
248 .to_owned()
249 .into_string()
250 .unwrap()));
251 }
252 Ok(res)
253 }
254 }
255
256 pub fn simple_search(&self, base: &str, scope: i32) -> Result<LDAPResponse, errors::LDAPError> {
266 self.ldap_search(base,
267 scope,
268 None,
269 None,
270 false,
271 None,
272 None,
273 ptr::null_mut(),
274 -1)
275 }
276
277 pub fn start_tls(&self, serverctrls: Option<*mut *mut LDAPControl>, clientctrls: Option<*mut *mut LDAPControl>) -> Result<i32, errors::LDAPError> {
301 let r_serverctrls = match serverctrls {
302 Some(sc) => sc,
303 None => ptr::null_mut(),
304 };
305
306 let r_clientctrls = match clientctrls {
307 Some(cc) => cc,
308 None => ptr::null_mut(),
309 };
310
311 unsafe {
312 let res = ldap_start_tls_s(self.ldap_ptr, r_serverctrls, r_clientctrls);
313
314 if res < 0 {
315 let raw_estr = ldap_err2string(res as c_int);
316 return Err(errors::LDAPError::NativeError(CStr::from_ptr(raw_estr)
317 .to_owned()
318 .into_string()
319 .unwrap()));
320 }
321
322 Ok(res)
323 }
324 }
325
326 pub fn ldap_search(&self,
344 base: &str,
345 scope: i32,
346 filter: Option<&str>,
347 attrs: Option<Vec<&str>>,
348 attrsonly: bool,
349 serverctrls: Option<*mut *mut LDAPControl>,
350 clientctrls: Option<*mut *mut LDAPControl>,
351 timeout: *mut timeval,
352 sizelimit: i32)
353 -> Result<LDAPResponse, errors::LDAPError> {
354
355 let mut ldap_msg = ptr::null_mut();
357
358 let filter_cstr: CString;
360 let r_filter = match filter {
361 Some(fs) => {
362 filter_cstr = CString::new(fs).unwrap();
363 filter_cstr.as_ptr()
364 }
365 None => ptr::null(),
366 };
367
368 let mut r_attrs: *const *const c_char = ptr::null();
370 let mut c_strs: Vec<CString> = Vec::new();
371 let mut r_attrs_ptrs: Vec<*const c_char> = Vec::new();
372
373 if let Some(strs) = attrs {
374 for string in strs {
375 c_strs.push(CString::new(string).unwrap());
377 r_attrs_ptrs.push(c_strs[c_strs.len() - 1].as_ptr());
379 }
380 r_attrs_ptrs.push(ptr::null());
382 r_attrs = r_attrs_ptrs.as_ptr();
383 }
384
385 let r_serverctrls = match serverctrls {
386 Some(sc) => sc,
387 None => ptr::null_mut(),
388 };
389
390 let r_clientctrls = match clientctrls {
391 Some(cc) => cc,
392 None => ptr::null_mut(),
393 };
394
395 let base = CString::new(base).unwrap();
396
397 unsafe {
398 let res: i32 = ldap_search_ext_s(self.ldap_ptr,
399 base.as_ptr(),
400 scope as c_int,
401 r_filter,
402 r_attrs,
403 attrsonly as c_int,
404 r_serverctrls,
405 r_clientctrls,
406 timeout,
407 sizelimit as c_int,
408 &mut ldap_msg);
409 if res != codes::results::LDAP_SUCCESS {
410 let raw_estr = ldap_err2string(res as c_int);
411 return Err(errors::LDAPError::NativeError(CStr::from_ptr(raw_estr)
412 .to_owned()
413 .into_string()
414 .unwrap()));
415 }
416 }
417
418 let mut resvec: Vec<HashMap<String, Vec<String>>> = vec![];
421 let mut entry = unsafe { ldap_first_entry(self.ldap_ptr, ldap_msg) };
422
423 while !entry.is_null() {
424
425 let mut map: HashMap<String, Vec<String>> = HashMap::new();
428 let mut ber: *mut BerElement = ptr::null_mut();
429 unsafe {
430 let raw_dn = ldap_get_dn(self.ldap_ptr, entry);
432 let mut dn: Vec<String> = Vec::new();
433 dn.push(CStr::from_ptr(raw_dn)
434 .to_owned()
435 .into_string()
436 .unwrap_or("<Invalid DN>".to_string()));
437 map.insert("dn".to_string(), dn);
438 ldap_memfree(raw_dn as *mut c_void);
439
440 let mut attr: *const c_char = ldap_first_attribute(self.ldap_ptr, entry, &mut ber);
441
442 while !attr.is_null() {
443
444 let key = CStr::from_ptr(attr).to_owned().into_string().unwrap();
446
447 let raw_vals: *const *const c_char =
449 ldap_get_values(self.ldap_ptr, entry, attr);
450 let raw_vals_len = ldap_count_values(raw_vals) as usize;
451 let val_slice: &[*const c_char] = slice::from_raw_parts(raw_vals, raw_vals_len);
452
453 let values: Vec<String> = val_slice.iter()
455 .map(|ptr| {
456 CStr::from_ptr(*ptr)
458 .to_owned()
459 .into_string()
460 .unwrap_or("<cannot parse binary data yet.>".to_string())
461 })
462 .collect();
463
464 map.insert(key, values);
466
467 ldap_value_free(raw_vals);
469 ldap_memfree(attr as *mut c_void);
470 attr = ldap_next_attribute(self.ldap_ptr, entry, ber)
471 }
472
473 ber_free(ber, 0);
475 entry = ldap_next_entry(self.ldap_ptr, entry);
476
477 }
478
479 resvec.push(map);
481
482 }
483
484 unsafe { ldap_msgfree(ldap_msg) };
486
487 Ok(resvec)
488 }
489}
490
491#[cfg(test)]
492mod tests {
493
494 use std::ptr;
495 use codes;
496
497 const TEST_ADDRESS: &'static str = "ldap://ldap.forumsys.com";
498 const TEST_BIND_DN: &'static str = "cn=read-only-admin,dc=example,dc=com";
499 const TEST_BIND_PASS: &'static str = "password";
500 const TEST_SIMPLE_SEARCH_QUERY: &'static str = "uid=tesla,dc=example,dc=com";
501 const TEST_SEARCH_BASE: &'static str = "dc=example,dc=com";
502 const TEST_SEARCH_FILTER: &'static str = "(uid=euler)";
503 const TEST_SEARCH_INVALID_FILTER: &'static str = "(uid=INVALID)";
504
505 #[test]
507 fn test_ldap_new() {
508 let _ = super::RustLDAP::new(TEST_ADDRESS).unwrap();
509 }
510
511 #[test]
513 fn test_invalid_ldap_new() {
514 if let Err(e) = super::RustLDAP::new("lda://localhost") {
515 assert_eq!(super::errors::LDAPError::NativeError("Bad parameter to an ldap routine"
516 .to_string()),
517 e);
518 } else {
519 assert!(false);
520 }
521 }
522
523 #[test]
524 #[should_panic]
525 fn test_invalid_cstring_ldap_new() {
526 let _ = super::RustLDAP::new("INVALID\0CSTRING").unwrap();
527 }
528
529 #[test]
530 fn test_simple_bind() {
531 let ldap = super::RustLDAP::new(TEST_ADDRESS).unwrap();
532 assert!(ldap.set_option(codes::options::LDAP_OPT_PROTOCOL_VERSION, &3));
533 let res = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).unwrap();
534 assert_eq!(codes::results::LDAP_SUCCESS, res);
535 println!("Bind result: {:?}", res);
536
537 }
538
539 #[test]
540 #[should_panic] fn test_simple_bind_with_start_tls() {
542 let ldap = super::RustLDAP::new(TEST_ADDRESS).unwrap();
543
544 assert!(ldap.set_option(codes::options::LDAP_OPT_PROTOCOL_VERSION, &3));
545 ldap.start_tls(None, None);
546
547 ldap.set_option(
548 codes::options::LDAP_OPT_PROTOCOL_VERSION,
549 &codes::versions::LDAP_VERSION3,
550 );
551
552 ldap.set_option(
553 codes::options::LDAP_OPT_X_TLS_REQUIRE_CERT,
554 &codes::options::LDAP_OPT_X_TLS_ALLOW,
555 );
556
557 ldap.set_option(codes::options::LDAP_OPT_X_TLS_NEWCTX, &0);
558
559 let res = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).unwrap();
560 assert_eq!(codes::results::LDAP_SUCCESS, res);
561 println!("Bind result: {:?}", res);
562
563 }
564
565
566 #[test]
567 fn test_simple_search() {
568
569 println!("Testing simple search");
570 let ldap = super::RustLDAP::new(TEST_ADDRESS).unwrap();
571 assert!(ldap.set_option(codes::options::LDAP_OPT_PROTOCOL_VERSION, &3));
572 let res = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).unwrap();
573 assert_eq!(codes::results::LDAP_SUCCESS, res);
574 let search_res =
575 ldap.simple_search(TEST_SIMPLE_SEARCH_QUERY, codes::scopes::LDAP_SCOPE_BASE).unwrap();
576
577 assert!(search_res.len() == 1);
579
580 assert_eq!(search_res[0]["dn"][0], "uid=tesla,dc=example,dc=com");
582
583 for result in search_res {
584 println!("simple search result: {:?}", result);
585 for (key, value) in result {
586 println!("- key: {:?}", key);
587 for res_val in value {
588 println!("- - res_val: {:?}", res_val);
589 }
590 }
591 }
592
593 }
594
595 #[test]
596 fn test_search() {
597
598 println!("Testing search");
599 let ldap = super::RustLDAP::new(TEST_ADDRESS).unwrap();
600 assert!(ldap.set_option(codes::options::LDAP_OPT_PROTOCOL_VERSION, &3));
601 let res = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).unwrap();
602 assert_eq!(codes::results::LDAP_SUCCESS, res);
603 let search_res = ldap.ldap_search(TEST_SEARCH_BASE,
604 codes::scopes::LDAP_SCOPE_SUB,
605 Some(TEST_SEARCH_FILTER),
606 None,
607 false,
608 None,
609 None,
610 ptr::null_mut(),
611 -1)
612 .unwrap();
613
614 assert!(search_res.len() == 1);
616
617 assert_eq!(search_res[0]["dn"][0], "uid=euler,dc=example,dc=com");
619
620 for result in search_res {
621 println!("search result: {:?}", result);
622 for (key, value) in result {
623 println!("- key: {:?}", key);
624 for res_val in value {
625 println!("- - res_val: {:?}", res_val);
626 }
627 }
628 }
629
630 }
631
632 #[test]
633 fn test_invalid_search() {
634
635 println!("Testing invalid search");
636 let ldap = super::RustLDAP::new(TEST_ADDRESS).unwrap();
637 assert!(ldap.set_option(codes::options::LDAP_OPT_PROTOCOL_VERSION, &3));
638 let res = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).unwrap();
639 assert_eq!(codes::results::LDAP_SUCCESS, res);
640 let search_res = ldap.ldap_search(TEST_SEARCH_BASE,
641 codes::scopes::LDAP_SCOPE_SUB,
642 Some(TEST_SEARCH_INVALID_FILTER),
643 None,
644 false,
645 None,
646 None,
647 ptr::null_mut(),
648 -1)
649 .unwrap();
650
651 assert!(search_res.len() == 0);
653
654 }
655
656 #[test]
657 fn test_search_attrs() {
658
659 println!("Testing search with attrs");
660 let test_search_attrs_vec = vec!["cn", "sn", "mail"];
661 let ldap = super::RustLDAP::new(TEST_ADDRESS).unwrap();
662 assert!(ldap.set_option(codes::options::LDAP_OPT_PROTOCOL_VERSION, &3));
663 let res = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).unwrap();
664 assert_eq!(codes::results::LDAP_SUCCESS, res);
665 let search_res = ldap.ldap_search(TEST_SEARCH_BASE,
666 codes::scopes::LDAP_SCOPE_SUB,
667 Some(TEST_SEARCH_FILTER),
668 Some(test_search_attrs_vec),
669 false,
670 None,
671 None,
672 ptr::null_mut(),
673 -1)
674 .unwrap();
675
676 assert!(search_res.len() == 1);
678
679 for result in search_res {
680 println!("attrs search result: {:?}", result);
681 for (key, value) in result {
682 println!("- key: {:?}", key);
683 for res_val in value {
684 println!("- - res_val: {:?}", res_val);
685 }
686 }
687 }
688
689 }
690
691 #[test]
692 fn test_search_invalid_attrs() {
693
694 println!("Testing search with invalid attrs");
695 let test_search_attrs_vec = vec!["cn", "sn", "mail", "INVALID"];
696 let ldap = super::RustLDAP::new(TEST_ADDRESS).unwrap();
697 assert!(ldap.set_option(codes::options::LDAP_OPT_PROTOCOL_VERSION, &3));
698 let res = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).unwrap();
699 assert_eq!(codes::results::LDAP_SUCCESS, res);
700 let search_res = ldap.ldap_search(TEST_SEARCH_BASE,
701 codes::scopes::LDAP_SCOPE_SUB,
702 Some(TEST_SEARCH_FILTER),
703 Some(test_search_attrs_vec),
704 false,
705 None,
706 None,
707 ptr::null_mut(),
708 -1)
709 .unwrap();
710
711 for result in search_res {
712 println!("attrs search result: {:?}", result);
713 for (key, value) in result {
714 println!("- key: {:?}", key);
715 for res_val in value {
716 println!("- - res_val: {:?}", res_val);
717 }
718 }
719 }
720
721 }
722
723}