diff --git a/Cargo.toml b/Cargo.toml index fbdef95..f376d08 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "openldap" version = "1.2.2" -authors = ["Josh Leverette ", "Ross Delinger ", "Stephen Holsapple ", "Yong Wen Chua "] +authors = ["Josh Leverette ", "Ross Delinger ", "Stephen Holsapple ", "Yong Wen Chua ", "Mathias Myrland "] license = "MIT" readme = "README.md" repository = "https://github.com/coder543/rust-cldap" @@ -11,3 +11,8 @@ description = "Straightforward Rust bindings to the C openldap library. This is [dependencies] libc = "0.2.10" + +[workspace] +members = [ + "examples/simple_bind_authentication" +] diff --git a/examples/simple_bind_authentication/Cargo.toml b/examples/simple_bind_authentication/Cargo.toml new file mode 100644 index 0000000..81b4fbc --- /dev/null +++ b/examples/simple_bind_authentication/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "simple_bind" +version = "0.1.0" +authors = ["Mathias Myrland "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = {git="https://github.com/clap-rs/clap.git"} +openldap = { path = "../../" } diff --git a/examples/simple_bind_authentication/README.md b/examples/simple_bind_authentication/README.md new file mode 100644 index 0000000..180c460 --- /dev/null +++ b/examples/simple_bind_authentication/README.md @@ -0,0 +1,33 @@ +# Simple Bind with start_tls + +This example shows how to use simple_bind in combination with start_tls +and a manager account to do a typical user authentication lookup. + +Start TLS is the recommended way to do secure LDAP; ldaps:// on port 636 is deprecated. + +## Running + +Start the example docker using the start_example_server.sh script from +the examples directory. Then, from the simple_bind directory, do + +```shell script +cargo run -- -u fry -p fry +``` + +## Steps that are being performed + +The first step is to set up the LDAPRust instance, and perform start_tls on it. +This ensures that our communication is encrypted. Note that we are not verifying +the server certificate in this example; this is something you should do in production. + +The next step is to simple_bind using our manger accounts DN and password. This +will allow us to perform an ldap_search later on. + +Now, we take the incoming user name string, and perform an ldap_search for it. +Note how we are matching either email or username. Our search yields the DN +for the provided credentials. + +The last step is to attempt a simple bind with the discovered DN and provided +user password. If all goes well, we are authenticated, otherwise, something +went wrong. + diff --git a/examples/simple_bind_authentication/src/main.rs b/examples/simple_bind_authentication/src/main.rs new file mode 100644 index 0000000..510f5da --- /dev/null +++ b/examples/simple_bind_authentication/src/main.rs @@ -0,0 +1,139 @@ +#[macro_use] +extern crate clap; + +extern crate openldap; + +use openldap::errors::*; +use openldap::*; +use std::ptr; + +#[derive(Clap)] +#[clap( + name = "LDAP simple_bind_authentication with start_tls authentication example", + author = "Mathias Myrland ", + version = "0.1.0" +)] +struct AuthOpts { + #[clap(short = "u")] + user: String, + + #[clap(short = "p")] + password: String, +} + +fn ldap_with_start_tls(ldap_uri: &str) -> Result { + let ldap = RustLDAP::new(ldap_uri).unwrap(); + + ldap.set_option( + codes::options::LDAP_OPT_PROTOCOL_VERSION, + &codes::versions::LDAP_VERSION3, + ); + + // WARNING: Normally you would want to verify the server certificate to avoid + // man in the middle attacks, but for this testing scenario we're using a + // generated self signed certificate from the docker container. + // + // To set up certificate validation, use the LDAP_OPT_X_TLS_CACERT* options + ldap.set_option( + codes::options::LDAP_OPT_X_TLS_REQUIRE_CERT, + &codes::options::LDAP_OPT_X_TLS_NEVER, + ); + + ldap.set_option(openldap::codes::options::LDAP_OPT_X_TLS_NEWCTX, &0); + + ldap.start_tls(None, None)?; + + Ok(ldap) +} + +fn do_simple_bind( + ldap: &RustLDAP, + ldap_manager_user: &str, + ldap_manager_pass: &str, +) -> Result<(), LDAPError> { + let bind_result = ldap.simple_bind(ldap_manager_user, ldap_manager_pass)?; + + match bind_result { + v if v == openldap::codes::results::LDAP_SUCCESS => Ok(()), + _ => Err(LDAPError::from(String::from( + "Authentication with simple bind failed", + ))), + } +} + +fn ldap_dn_lookup(ldap: &RustLDAP, who: &str) -> Result { + // First, escape the who parameter to prevent LDAP injection attacks + let safe_who = escape_filter_assertion_value(who)?; + + // Show all DNs matching the description "Human" + // ldap_search is a powerful query language, look at + // https://confluence.atlassian.com/kb/how-to-write-ldap-search-filters-792496933.html + // for an overview + // + // This particular filter allows the user to sign in with either + // uid or email + let filter = format!("(|(uid={})(mail={}))", safe_who, safe_who); + + match ldap.ldap_search( + "ou=people,dc=planetexpress,dc=com", + codes::scopes::LDAP_SCOPE_SUBTREE, + Some(filter.as_str()), + Some(vec!["dn"]), + true, + None, + None, + ptr::null_mut(), + -1, + ) { + Ok(search_results) => { + for result_map in search_results { + for result_tuple in result_map { + println!("Found result map with key {}", result_tuple.0); + for result_data in result_tuple.1 { + println!("\t {}", result_data); + return Ok(result_data); + } + } + } + + Err(LDAPError::from(String::from( + "Authentication with simple bind failed", + ))) + } + _ => Err(LDAPError::from(String::from( + "Authentication with simple bind failed", + ))), + } +} + +fn main() { + let options = AuthOpts::parse(); + let user_to_authenticate = options.user; + let pwd_to_authenticate = options.password; + + let ldap_uri = "ldap://localhost:389"; + let ldap_manager_dn = "cn=Hubert J. Farnsworth,ou=people,dc=planetexpress,dc=com"; + let ldap_manager_pass = "professor"; + + let ldap = ldap_with_start_tls(ldap_uri).unwrap(); + + // Bind to the LDAP server with the manager account, + // this is done to perform a search for the DN to + // use when authenticating the user attempting to + // sign in. Obviously, the manager credentials should + // be kept secret, and not be put under version control. + // In our test scenario, the professor is the manager. + do_simple_bind(&ldap, ldap_manager_dn, ldap_manager_pass).unwrap(); + + let (dn, passwd, valid) = match ldap_dn_lookup(&ldap, user_to_authenticate.as_str()) { + Ok(dn) => (dn, pwd_to_authenticate, true), + _ => ("".into(), "".into(), false), + }; + + // We do the simple bind regardless of the user existence, to protect against timing attacks + // to probe existing users + match do_simple_bind(&ldap, dn.as_str(), passwd.as_str()).is_ok() && valid { + true => println!("Successfully signed in as {}", dn), + false => println!("Could not log in"), + } +} diff --git a/examples/start_example_server.sh b/examples/start_example_server.sh new file mode 100755 index 0000000..d0cfbe4 --- /dev/null +++ b/examples/start_example_server.sh @@ -0,0 +1,3 @@ +#! /usr/bin/env bash + +docker run -p 389:389 -p 636:636 rroemhild/test-openldap diff --git a/src/errors.rs b/src/errors.rs index 65cdb99..1e9fcc2 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,9 +1,8 @@ //! Errors and trait implementations. //! -use std::fmt; -use std::error; use std::convert; - +use std::error; +use std::fmt; /// A LDAP error. /// @@ -40,7 +39,7 @@ impl error::Error for LDAPError { /// /// Note, currently this method always return `None` as we do not know the root cause of the /// error. - fn cause(&self) -> Option<&error::Error> { + fn cause(&self) -> Option<&dyn error::Error> { None } } diff --git a/src/lib.rs b/src/lib.rs index d0b1406..3785816 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,13 +4,13 @@ //! LDAP directory. //! extern crate libc; -use libc::{c_int, c_char, c_void, timeval}; + +use libc::{c_char, c_int, c_void, timeval}; +use std::boxed; use std::collections::HashMap; use std::ffi::{CStr, CString}; use std::ptr; use std::slice; -use std::boxed; -use std::ptr::null_mut; pub mod codes; pub mod errors; @@ -28,6 +28,7 @@ pub struct LDAPControl; struct BerElement; unsafe impl Sync for LDAP {} + unsafe impl Send for LDAP {} #[link(name = "lber")] @@ -47,41 +48,48 @@ extern "C" { fn ldap_first_entry(ldap: *mut LDAP, result: *mut LDAPMessage) -> *mut LDAPMessage; fn ldap_next_entry(ldap: *mut LDAP, entry: *mut LDAPMessage) -> *mut LDAPMessage; fn ldap_get_dn(ldap: *mut LDAP, entry: *mut LDAPMessage) -> *const c_char; - fn ldap_get_values(ldap: *mut LDAP, - entry: *mut LDAPMessage, - attr: *const c_char) - -> *const *const c_char; + fn ldap_get_values( + ldap: *mut LDAP, + entry: *mut LDAPMessage, + attr: *const c_char, + ) -> *const *const c_char; fn ldap_count_values(vals: *const *const c_char) -> c_int; fn ldap_value_free(vals: *const *const c_char); fn ldap_set_option(ldap: *const LDAP, option: c_int, invalue: *const c_void) -> c_int; fn ldap_simple_bind_s(ldap: *mut LDAP, who: *const c_char, pass: *const c_char) -> c_int; - fn ldap_first_attribute(ldap: *mut LDAP, - entry: *mut LDAPMessage, - berptr: *mut *mut BerElement) - -> *const c_char; - fn ldap_next_attribute(ldap: *mut LDAP, - entry: *mut LDAPMessage, - berptr: *mut BerElement) - -> *const c_char; - fn ldap_search_ext_s(ldap: *mut LDAP, - base: *const c_char, - scope: c_int, - filter: *const c_char, - attrs: *const *const c_char, - attrsonly: c_int, - serverctrls: *mut *mut LDAPControl, - clientctrls: *mut *mut LDAPControl, - timeout: *mut timeval, - sizelimit: c_int, - res: *mut *mut LDAPMessage) - -> c_int; - fn ldap_unbind_ext_s(ldap: *mut LDAP, - sctrls: *mut *mut LDAPControl, - cctrls: *mut *mut LDAPControl) - -> c_int; - fn ldap_start_tls_s(ldap: *mut LDAP, - scrtrls: *mut *mut LDAPControl, - cctrls: *mut *mut LDAPControl) -> c_int; + fn ldap_first_attribute( + ldap: *mut LDAP, + entry: *mut LDAPMessage, + berptr: *mut *mut BerElement, + ) -> *const c_char; + fn ldap_next_attribute( + ldap: *mut LDAP, + entry: *mut LDAPMessage, + berptr: *mut BerElement, + ) -> *const c_char; + fn ldap_search_ext_s( + ldap: *mut LDAP, + base: *const c_char, + scope: c_int, + filter: *const c_char, + attrs: *const *const c_char, + attrsonly: c_int, + serverctrls: *mut *mut LDAPControl, + clientctrls: *mut *mut LDAPControl, + timeout: *mut timeval, + sizelimit: c_int, + res: *mut *mut LDAPMessage, + ) -> c_int; + fn ldap_unbind_ext_s( + ldap: *mut LDAP, + sctrls: *mut *mut LDAPControl, + cctrls: *mut *mut LDAPControl, + ) -> c_int; + fn ldap_start_tls_s( + ldap: *mut LDAP, + scrtrls: *mut *mut LDAPControl, + cctrls: *mut *mut LDAPControl, + ) -> c_int; } /// A typedef for an `LDAPResponse` type. @@ -91,7 +99,6 @@ extern "C" { /// pub type LDAPResponse = Vec>>; - /// A high level abstraction over the raw `OpenLDAP` functions. /// /// A `RustLDAP` object hides raw `OpenLDAP` complexities and exposes a simple object that is @@ -105,8 +112,8 @@ pub struct RustLDAP { ldap_ptr: *mut LDAP, } - unsafe impl Sync for RustLDAP {} + unsafe impl Send for RustLDAP {} impl Drop for RustLDAP { @@ -181,7 +188,6 @@ impl RustLDAP { /// * uri - URI of the LDAP server to connect to. E.g., ldaps://localhost:636. /// pub fn new(uri: &str) -> Result { - // Create some space for the LDAP pointer. let mut cldap = ptr::null_mut(); @@ -191,12 +197,10 @@ impl RustLDAP { let res = ldap_initialize(&mut cldap, uri_cstring.as_ptr()); if res != codes::results::LDAP_SUCCESS { let raw_estr = ldap_err2string(res as c_int); - return Err(errors::LDAPError::NativeError(CStr::from_ptr(raw_estr) - .to_owned() - .into_string() - .unwrap())); + return Err(errors::LDAPError::NativeError( + CStr::from_ptr(raw_estr).to_owned().into_string().unwrap(), + )); } - } Ok(RustLDAP { ldap_ptr: cldap }) @@ -244,10 +248,9 @@ impl RustLDAP { let res = ldap_simple_bind_s(self.ldap_ptr, who_ptr, pass_ptr); if res < 0 { let raw_estr = ldap_err2string(res as c_int); - return Err(errors::LDAPError::NativeError(CStr::from_ptr(raw_estr) - .to_owned() - .into_string() - .unwrap())); + return Err(errors::LDAPError::NativeError( + CStr::from_ptr(raw_estr).to_owned().into_string().unwrap(), + )); } Ok(res) } @@ -263,15 +266,17 @@ impl RustLDAP { /// * scope - The search scope. See `cldap::codes::scopes`. /// pub fn simple_search(&self, base: &str, scope: i32) -> Result { - self.ldap_search(base, - scope, - None, - None, - false, - None, - None, - ptr::null_mut(), - -1) + self.ldap_search( + base, + scope, + None, + None, + false, + None, + None, + ptr::null_mut(), + -1, + ) } /// Installs TLS handlers on the session @@ -297,7 +302,11 @@ impl RustLDAP { /// ldap.start_tls(None, None); /// ldap.simple_bind("some-dn", "some-password").unwrap(); /// ``` - pub fn start_tls(&self, serverctrls: Option<*mut *mut LDAPControl>, clientctrls: Option<*mut *mut LDAPControl>) -> Result { + pub fn start_tls( + &self, + serverctrls: Option<*mut *mut LDAPControl>, + clientctrls: Option<*mut *mut LDAPControl>, + ) -> Result { let r_serverctrls = match serverctrls { Some(sc) => sc, None => ptr::null_mut(), @@ -313,10 +322,9 @@ impl RustLDAP { if res < 0 { let raw_estr = ldap_err2string(res as c_int); - return Err(errors::LDAPError::NativeError(CStr::from_ptr(raw_estr) - .to_owned() - .into_string() - .unwrap())); + return Err(errors::LDAPError::NativeError( + CStr::from_ptr(raw_estr).to_owned().into_string().unwrap(), + )); } Ok(res) @@ -340,18 +348,18 @@ impl RustLDAP { /// * timeout - A timeout. /// * sizelimit - The maximum number of entities to return, or -1 for no limit. /// - pub fn ldap_search(&self, - base: &str, - scope: i32, - filter: Option<&str>, - attrs: Option>, - attrsonly: bool, - serverctrls: Option<*mut *mut LDAPControl>, - clientctrls: Option<*mut *mut LDAPControl>, - timeout: *mut timeval, - sizelimit: i32) - -> Result { - + pub fn ldap_search( + &self, + base: &str, + scope: i32, + filter: Option<&str>, + attrs: Option>, + attrsonly: bool, + serverctrls: Option<*mut *mut LDAPControl>, + clientctrls: Option<*mut *mut LDAPControl>, + timeout: *mut timeval, + sizelimit: i32, + ) -> Result { // Make room for the LDAPMessage, being sure to delete this before we return. let mut ldap_msg = ptr::null_mut(); @@ -395,23 +403,24 @@ impl RustLDAP { let base = CString::new(base).unwrap(); unsafe { - let res: i32 = ldap_search_ext_s(self.ldap_ptr, - base.as_ptr(), - scope as c_int, - r_filter, - r_attrs, - attrsonly as c_int, - r_serverctrls, - r_clientctrls, - timeout, - sizelimit as c_int, - &mut ldap_msg); + let res: i32 = ldap_search_ext_s( + self.ldap_ptr, + base.as_ptr(), + scope as c_int, + r_filter, + r_attrs, + attrsonly as c_int, + r_serverctrls, + r_clientctrls, + timeout, + sizelimit as c_int, + &mut ldap_msg, + ); if res != codes::results::LDAP_SUCCESS { let raw_estr = ldap_err2string(res as c_int); - return Err(errors::LDAPError::NativeError(CStr::from_ptr(raw_estr) - .to_owned() - .into_string() - .unwrap())); + return Err(errors::LDAPError::NativeError( + CStr::from_ptr(raw_estr).to_owned().into_string().unwrap(), + )); } } @@ -421,7 +430,6 @@ impl RustLDAP { let mut entry = unsafe { ldap_first_entry(self.ldap_ptr, ldap_msg) }; while !entry.is_null() { - // Make the map holding the attribute : value pairs as well as the BerElement that keeps // track of what position we're in let mut map: HashMap> = HashMap::new(); @@ -430,17 +438,18 @@ impl RustLDAP { // Populate the "DN" of the user let raw_dn = ldap_get_dn(self.ldap_ptr, entry); let mut dn: Vec = Vec::new(); - dn.push(CStr::from_ptr(raw_dn) - .to_owned() - .into_string() - .unwrap_or("".to_string())); + dn.push( + CStr::from_ptr(raw_dn) + .to_owned() + .into_string() + .unwrap_or("".to_string()), + ); map.insert("dn".to_string(), dn); ldap_memfree(raw_dn as *mut c_void); let mut attr: *const c_char = ldap_first_attribute(self.ldap_ptr, entry, &mut ber); while !attr.is_null() { - // Convert the attribute into a Rust string. let key = CStr::from_ptr(attr).to_owned().into_string().unwrap(); @@ -451,7 +460,8 @@ impl RustLDAP { let val_slice: &[*const c_char] = slice::from_raw_parts(raw_vals, raw_vals_len); // Map these into a vector of Strings. - let values: Vec = val_slice.iter() + let values: Vec = val_slice + .iter() .map(|ptr| { // TODO(sholsapp): If this contains binary data this will fail. CStr::from_ptr(*ptr) @@ -473,12 +483,10 @@ impl RustLDAP { // Free the BerElement and advance to the next entry. ber_free(ber, 0); entry = ldap_next_entry(self.ldap_ptr, entry); - } // Push this entry into the vector. resvec.push(map); - } // Make sure we free the message and return the parsed results. @@ -488,11 +496,56 @@ impl RustLDAP { } } +/// This method properly escapes a value to be placed in a search filter, +/// to avoid LDAP injection attacks, according to https://tools.ietf.org/search/rfc4515#section-3 UTF1SUBSET +/// +/// # Examples +/// ``` +/// use openldap::escape_filter_assertion_value; +/// +/// fn make_a_search_filter() { +/// // This value would be provided by a malicious user +/// let malicious_input = r"doesnotmatter)(isMemberOf=cn=admins,ou=groups,ou=example,ou=org)(doesnotmatter="; +/// let safe_filter_value = escape_filter_assertion_value(malicious_input).unwrap(); +/// +/// // This will now be safe, as it is not possible to perform an LDAP injection attack +/// // using the filter value +/// let filter = format!("(|(uid={})(mail={}))", safe_filter_value, safe_filter_value); +/// +/// assert_eq!(safe_filter_value, r"doesnotmatter\29\28isMemberOf=cn=admins,ou=groups,ou=example,ou=org\29\28doesnotmatter=") +/// } +/// ``` +pub fn escape_filter_assertion_value(input: &str) -> Vec { + fn to_hex(byte: u8) -> u8 { + match byte { + 0..=9 => b'0' + byte, + _ => b'a' + byte - 10, + } + } + + input + .bytes() + .flat_map(|c| match c { + b'\0' => vec![b'\\', b'0', b'0'], + b'!' => vec![b'\\', b'2', b'1'], + b'&' => vec![b'\\', b'2', b'6'], + b'(' => vec![b'\\', b'2', b'8'], + b')' => vec![b'\\', b'2', b'9'], + b'*' => vec![b'\\', b'2', b'a'], + b'\\' => vec![b'\\', b'5', b'c'], + b':' => vec![b'\\', b'3', b'a'], + b'|' => vec![b'\\', b'7', b'c'], + b'~' => vec![b'\\', b'7', b'e'], + 0x7F..=0xFF => vec![b'\\', to_hex(c >> 4), to_hex(c & 0xf)], + _ => vec![c], + }) + .collect() +} + #[cfg(test)] mod tests { - use std::ptr; - use codes; + use {codes, escape_filter_assertion_value}; const TEST_ADDRESS: &'static str = "ldap://ldap.forumsys.com"; const TEST_BIND_DN: &'static str = "cn=read-only-admin,dc=example,dc=com"; @@ -512,9 +565,12 @@ mod tests { #[test] fn test_invalid_ldap_new() { if let Err(e) = super::RustLDAP::new("lda://localhost") { - assert_eq!(super::errors::LDAPError::NativeError("Bad parameter to an ldap routine" - .to_string()), - e); + assert_eq!( + super::errors::LDAPError::NativeError( + "Bad parameter to an ldap routine".to_string() + ), + e + ); } else { assert!(false); } @@ -533,7 +589,6 @@ mod tests { let res = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).unwrap(); assert_eq!(codes::results::LDAP_SUCCESS, res); println!("Bind result: {:?}", res); - } #[test] @@ -542,7 +597,7 @@ mod tests { let ldap = super::RustLDAP::new(TEST_ADDRESS).unwrap(); assert!(ldap.set_option(codes::options::LDAP_OPT_PROTOCOL_VERSION, &3)); - ldap.start_tls(None, None); + ldap.start_tls(None, None).unwrap(); ldap.set_option( codes::options::LDAP_OPT_PROTOCOL_VERSION, @@ -559,20 +614,18 @@ mod tests { let res = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).unwrap(); assert_eq!(codes::results::LDAP_SUCCESS, res); println!("Bind result: {:?}", res); - } - #[test] fn test_simple_search() { - println!("Testing simple search"); let ldap = super::RustLDAP::new(TEST_ADDRESS).unwrap(); assert!(ldap.set_option(codes::options::LDAP_OPT_PROTOCOL_VERSION, &3)); let res = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).unwrap(); assert_eq!(codes::results::LDAP_SUCCESS, res); - let search_res = - ldap.simple_search(TEST_SIMPLE_SEARCH_QUERY, codes::scopes::LDAP_SCOPE_BASE).unwrap(); + let search_res = ldap + .simple_search(TEST_SIMPLE_SEARCH_QUERY, codes::scopes::LDAP_SCOPE_BASE) + .unwrap(); //make sure we got something back assert!(search_res.len() == 1); @@ -589,26 +642,27 @@ mod tests { } } } - } #[test] fn test_search() { - println!("Testing search"); let ldap = super::RustLDAP::new(TEST_ADDRESS).unwrap(); assert!(ldap.set_option(codes::options::LDAP_OPT_PROTOCOL_VERSION, &3)); let res = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).unwrap(); assert_eq!(codes::results::LDAP_SUCCESS, res); - let search_res = ldap.ldap_search(TEST_SEARCH_BASE, - codes::scopes::LDAP_SCOPE_SUB, - Some(TEST_SEARCH_FILTER), - None, - false, - None, - None, - ptr::null_mut(), - -1) + let search_res = ldap + .ldap_search( + TEST_SEARCH_BASE, + codes::scopes::LDAP_SCOPE_SUB, + Some(TEST_SEARCH_FILTER), + None, + false, + None, + None, + ptr::null_mut(), + -1, + ) .unwrap(); //make sure we got something back @@ -626,51 +680,53 @@ mod tests { } } } - } #[test] fn test_invalid_search() { - println!("Testing invalid search"); let ldap = super::RustLDAP::new(TEST_ADDRESS).unwrap(); assert!(ldap.set_option(codes::options::LDAP_OPT_PROTOCOL_VERSION, &3)); let res = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).unwrap(); assert_eq!(codes::results::LDAP_SUCCESS, res); - let search_res = ldap.ldap_search(TEST_SEARCH_BASE, - codes::scopes::LDAP_SCOPE_SUB, - Some(TEST_SEARCH_INVALID_FILTER), - None, - false, - None, - None, - ptr::null_mut(), - -1) + let search_res = ldap + .ldap_search( + TEST_SEARCH_BASE, + codes::scopes::LDAP_SCOPE_SUB, + Some(TEST_SEARCH_INVALID_FILTER), + None, + false, + None, + None, + ptr::null_mut(), + -1, + ) .unwrap(); //make sure we got something back assert!(search_res.len() == 0); - } #[test] fn test_search_attrs() { - println!("Testing search with attrs"); let test_search_attrs_vec = vec!["cn", "sn", "mail"]; let ldap = super::RustLDAP::new(TEST_ADDRESS).unwrap(); assert!(ldap.set_option(codes::options::LDAP_OPT_PROTOCOL_VERSION, &3)); let res = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).unwrap(); assert_eq!(codes::results::LDAP_SUCCESS, res); - let search_res = ldap.ldap_search(TEST_SEARCH_BASE, - codes::scopes::LDAP_SCOPE_SUB, - Some(TEST_SEARCH_FILTER), - Some(test_search_attrs_vec), - false, - None, - None, - ptr::null_mut(), - -1) + let search_res = ldap + .ldap_search( + TEST_SEARCH_BASE, + codes::scopes::LDAP_SCOPE_SUB, + Some(TEST_SEARCH_FILTER), + Some(test_search_attrs_vec), + false, + None, + None, + ptr::null_mut(), + -1, + ) .unwrap(); //make sure we got something back @@ -685,27 +741,28 @@ mod tests { } } } - } #[test] fn test_search_invalid_attrs() { - println!("Testing search with invalid attrs"); let test_search_attrs_vec = vec!["cn", "sn", "mail", "INVALID"]; let ldap = super::RustLDAP::new(TEST_ADDRESS).unwrap(); assert!(ldap.set_option(codes::options::LDAP_OPT_PROTOCOL_VERSION, &3)); let res = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).unwrap(); assert_eq!(codes::results::LDAP_SUCCESS, res); - let search_res = ldap.ldap_search(TEST_SEARCH_BASE, - codes::scopes::LDAP_SCOPE_SUB, - Some(TEST_SEARCH_FILTER), - Some(test_search_attrs_vec), - false, - None, - None, - ptr::null_mut(), - -1) + let search_res = ldap + .ldap_search( + TEST_SEARCH_BASE, + codes::scopes::LDAP_SCOPE_SUB, + Some(TEST_SEARCH_FILTER), + Some(test_search_attrs_vec), + false, + None, + None, + ptr::null_mut(), + -1, + ) .unwrap(); for result in search_res { @@ -717,7 +774,22 @@ mod tests { } } } - } + #[test] + fn test_search_filter_escapation() { + let input_a = r"*\)(&!|~unaltered"; + let expected_filtered_a = r"\2a\5c\29\28\26\21\7c\7eunaltered"; + + let input_b = "thisshouldnotbealtered...[][]---,;;;"; + + let filtered_input_a = String::from_utf8(escape_filter_assertion_value(input_a)).unwrap(); + let filtered_input_b = String::from_utf8(escape_filter_assertion_value(input_b)).unwrap(); + assert_eq!( + String::from_utf8(escape_filter_assertion_value("🌏")).unwrap(), + r"\f0\9f\8c\8f", + ); + assert_eq!(expected_filtered_a, filtered_input_a); + assert_eq!(input_b, filtered_input_b); + } }