openldap/
lib.rs

1//! Objects for connecting and querying LDAP servers using `OpenLDAP`.
2//!
3//! Current support includes connection, initializing, binding, configuring, and search against an
4//! LDAP directory.
5//!
6extern 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
87/// A typedef for an `LDAPResponse` type.
88///
89/// LDAP responses are organized as vectors of mached entities. Typically, each entity is
90/// represented as a map of attributes to list of values.
91///
92pub type LDAPResponse = Vec<HashMap<String, Vec<String>>>;
93
94
95/// A high level abstraction over the raw `OpenLDAP` functions.
96///
97/// A `RustLDAP` object hides raw `OpenLDAP` complexities and exposes a simple object that is
98/// created, configured, and queried. Methods that call underlying `OpenLDAP` calls that can fail
99/// will raise an `errors::LDAPError` with additional details.
100///
101/// Using a `RustLDAP` object is easy!
102///
103pub struct RustLDAP {
104    /// A pointer to the underlying `OpenLDAP` object.
105    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        // Unbind the LDAP connection, making the C library free the LDAP*.
115        let rc = unsafe { ldap_unbind_ext_s(self.ldap_ptr, ptr::null_mut(), ptr::null_mut()) };
116
117        // Make sure it actually happened.
118        if rc != codes::results::LDAP_SUCCESS {
119            unsafe {
120                // Hopefully this never happens.
121                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
128/// A trait for types that can be passed as LDAP option values.
129///
130/// Underlying `OpenLDAP` implementation calls for option values to be passed in as `*const c_void`,
131/// while allowing values to be `i32` or `String`. Using traits, we implement function overloading to
132/// handle `i32` and `String` option value types.
133///
134/// This trait allocates memory that a caller must free using `std::boxed::Box::from_raw`. This
135/// helps guarantee that there is not a use after free bug (in Rust) while providing the appearance
136/// of opaque memory to `OpenLDAP` (in C). In pure C, we would've accomplished this by casting a
137/// local variable to a `const void *`. In Rust, we must do this on the heap to ensure Rust's
138/// ownership system does not free the memory used to store the option value between now and when
139/// the option is actually set.
140///
141pub 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    /// Create a new `RustLDAP`.
173    ///
174    /// Creates a new `RustLDAP` and initializes underlying `OpenLDAP` library. Upon creation, a
175    /// subsequent calls to `set_option` and `simple_bind` are possible. Before calling a search
176    /// related function, one must bind to the server by calling `simple_bind`. See module usage
177    /// information for more details on using a `RustLDAP` object.
178    ///
179    /// # Parameters
180    ///
181    /// * uri - URI of the LDAP server to connect to. E.g., ldaps://localhost:636.
182    ///
183    pub fn new(uri: &str) -> Result<RustLDAP, errors::LDAPError> {
184
185        // Create some space for the LDAP pointer.
186        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    /// Sets an option on the LDAP connection.
206    ///
207    /// When setting an option to _ON_ or _OFF_ one may use the boolean values `true` or `false`,
208    /// respectively.
209    ///
210    /// # Parameters
211    ///
212    /// * option - An option identifier from `cldap::codes`.
213    /// * value - The value to set for the option.
214    ///
215    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            // Allows for memory to be dropped when this binding goes away.
221            let _ = boxed::Box::from_raw(ptr as *mut c_void);
222            res == 0
223        }
224    }
225
226    /// Bind to the LDAP server.
227    ///
228    /// If you wish to configure options on the LDAP server, be sure to set required options using
229    ///`set_option` _before_ binding to the LDAP server. In some advanced cases, it may be required
230    /// to set multiple options for an option to be made available. Refer to the `OpenLDAP`
231    /// documentation for information on available options and how to use them.
232    ///
233    /// # Parameters
234    ///
235    /// * who - The user's name to bind with.
236    /// * pass - The user's password to bind with.
237    ///
238    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    /// Simple synchronous search.
257    ///
258    /// Performs a simple search with only the base, returning all attributes found.
259    ///
260    /// # Parameters
261    ///
262    /// * base - The LDAP base.
263    /// * scope - The search scope. See `cldap::codes::scopes`.
264    ///
265    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    /// Installs TLS handlers on the session
278    ///
279    /// # Examples
280    ///
281    /// ```should_panic
282    /// use openldap::RustLDAP;
283    /// let ldap = RustLDAP::new(&"ldaps://myserver:636").unwrap();
284    ///
285    /// ldap.set_option(
286    ///     openldap::codes::options::LDAP_OPT_PROTOCOL_VERSION,
287    ///     &openldap::codes::versions::LDAP_VERSION3,
288    /// );
289    ///
290    /// ldap.set_option(
291    ///     openldap::codes::options::LDAP_OPT_X_TLS_REQUIRE_CERT,
292    ///     &openldap::codes::options::LDAP_OPT_X_TLS_ALLOW,
293    /// );
294    ///
295    /// ldap.set_option(openldap::codes::options::LDAP_OPT_X_TLS_NEWCTX, &0);
296    ///
297    /// ldap.start_tls(None, None);
298    /// ldap.simple_bind("some-dn", "some-password").unwrap();
299    /// ```
300    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    /// Advanced synchronous search.
327    ///
328    /// Exposes a raw API around the underlying `ldap_search_ext_s` function from `OpenLDAP`.
329    /// Wherever possible, use provided wrappers.
330    ///
331    /// # Parameters
332    ///
333    /// * base - The base domain.
334    /// * scope - The search scope. See `cldap::codes::scopes`.
335    /// * filter - An optional filter.
336    /// * attrs - An optional set of attrs.
337    /// * attrsonly - True if should return only the attrs specified in `attrs`.
338    /// * serverctrls - Optional sever controls.
339    /// * clientctrls - Optional client controls.
340    /// * timeout - A timeout.
341    /// * sizelimit - The maximum number of entities to return, or -1 for no limit.
342    ///
343    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        // Make room for the LDAPMessage, being sure to delete this before we return.
356        let mut ldap_msg = ptr::null_mut();
357
358        // Convert the passed in filter sting to either a C-string or null if one is not passed.
359        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        // Convert the vec of attributes into the null-terminated array that the library expects.
369        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                // Create new CString and take ownership of it in c_strs.
376                c_strs.push(CString::new(string).unwrap());
377                // Create a pointer to that CString's raw data and store it in r_attrs.
378                r_attrs_ptrs.push(c_strs[c_strs.len() - 1].as_ptr());
379            }
380            // Ensure that there is a null value at the end of the vector.
381            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        // We now have to parse the results, copying the C-strings into Rust ones making sure to
419        // free the C-strings afterwards
420        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            // Make the map holding the attribute : value pairs as well as the BerElement that keeps
426            // track of what position we're in
427            let mut map: HashMap<String, Vec<String>> = HashMap::new();
428            let mut ber: *mut BerElement = ptr::null_mut();
429            unsafe {
430                // Populate the "DN" of the user
431                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                    // Convert the attribute into a Rust string.
445                    let key = CStr::from_ptr(attr).to_owned().into_string().unwrap();
446
447                    // Get the attribute values from LDAP.
448                    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                    // Map these into a vector of Strings.
454                    let values: Vec<String> = val_slice.iter()
455                        .map(|ptr| {
456                            // TODO(sholsapp): If this contains binary data this will fail.
457                            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                    // Insert newly constructed Rust key-value strings.
465                    map.insert(key, values);
466
467                    // Free the attr and value, then get next attr.
468                    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                // Free the BerElement and advance to the next entry.
474                ber_free(ber, 0);
475                entry = ldap_next_entry(self.ldap_ptr, entry);
476
477            }
478
479            // Push this entry into the vector.
480            resvec.push(map);
481
482        }
483
484        // Make sure we free the message and return the parsed results.
485        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 creating a RustLDAP struct with a valid uri.
506    #[test]
507    fn test_ldap_new() {
508        let _ = super::RustLDAP::new(TEST_ADDRESS).unwrap();
509    }
510
511    /// Test creating a RustLDAP struct with an invalid uri.
512    #[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] // the TEST_ADDRESS being used doesn't support LDAPS, only LDAP
541    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        //make sure we got something back
578        assert!(search_res.len() == 1);
579
580        // make sure the DN is searched and returned correctly
581        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        //make sure we got something back
615        assert!(search_res.len() == 1);
616
617        // make sure the DN is searched and returned correctly
618        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        //make sure we got something back
652        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        //make sure we got something back
677        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}