From 217f3aadf0f310b13b8911eaf9136ec9c898adde Mon Sep 17 00:00:00 2001 From: Mathias Myrland Date: Sun, 19 Jan 2020 14:29:37 +0100 Subject: [PATCH 1/8] (docker-based-examples) Added simple_bind with start_tls and manager account authentication example --- Cargo.toml | 7 +- examples/simple_bind_authentication/README.md | 33 ++ .../simple_bind_authentication/src/main.rs | 132 ++++++++ examples/start_example_server.sh | 3 + src/lib.rs | 316 +++++++++--------- 5 files changed, 336 insertions(+), 155 deletions(-) create mode 100644 examples/simple_bind_authentication/README.md create mode 100644 examples/simple_bind_authentication/src/main.rs create mode 100755 examples/start_example_server.sh 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/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..4cf16e3 --- /dev/null +++ b/examples/simple_bind_authentication/src/main.rs @@ -0,0 +1,132 @@ +#[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 { + // 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={}))", who, 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(); + + if let Ok(fry_dn) = ldap_dn_lookup(&ldap, user_to_authenticate.as_str()) { + // Now, perform a bind with the DN we found matching the user attempting to sign in + // and the password provided in the authentication request + do_simple_bind(&ldap, fry_dn.as_str(), pwd_to_authenticate.as_str()).unwrap(); + + println!("Successfully signed in as fry"); + } +} 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/lib.rs b/src/lib.rs index d0b1406..edac895 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; +use std::slice; pub mod codes; pub mod errors; @@ -47,41 +47,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 +98,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,7 +111,6 @@ pub struct RustLDAP { ldap_ptr: *mut LDAP, } - unsafe impl Sync for RustLDAP {} unsafe impl Send for RustLDAP {} @@ -172,8 +177,8 @@ impl RustLDAP { /// Create a new `RustLDAP`. /// /// Creates a new `RustLDAP` and initializes underlying `OpenLDAP` library. Upon creation, a - /// subsequent calls to `set_option` and `simple_bind` are possible. Before calling a search - /// related function, one must bind to the server by calling `simple_bind`. See module usage + /// subsequent calls to `set_option` and `simple_bind_authentication` are possible. Before calling a search + /// related function, one must bind to the server by calling `simple_bind_authentication`. See module usage /// information for more details on using a `RustLDAP` object. /// /// # Parameters @@ -181,7 +186,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 +195,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 +246,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 +264,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 +300,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 +320,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 +346,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 +401,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 +428,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 +436,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 +458,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 +481,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. @@ -491,8 +497,8 @@ impl RustLDAP { #[cfg(test)] mod tests { - use std::ptr; use codes; + use std::ptr; 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 +518,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 +542,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] @@ -559,20 +567,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 +595,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 +633,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 +694,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 +727,5 @@ mod tests { } } } - } - } From c7cdf6621fa0a99d9c911c84d7576bdaf32d4b4d Mon Sep 17 00:00:00 2001 From: Mathias Myrland Date: Sun, 19 Jan 2020 14:32:38 +0100 Subject: [PATCH 2/8] (docker-based-examples) reverted accidental rustfmt on lib.rs --- src/lib.rs | 316 ++++++++++++++++++++++++++--------------------------- 1 file changed, 154 insertions(+), 162 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index edac895..d0b1406 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,13 +4,13 @@ //! LDAP directory. //! extern crate libc; -use libc::{c_char, c_int, c_void, timeval}; -use std::boxed; +use libc::{c_int, c_char, c_void, timeval}; use std::collections::HashMap; use std::ffi::{CStr, CString}; use std::ptr; -use std::ptr::null_mut; use std::slice; +use std::boxed; +use std::ptr::null_mut; pub mod codes; pub mod errors; @@ -47,48 +47,41 @@ 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. @@ -98,6 +91,7 @@ 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 @@ -111,6 +105,7 @@ pub struct RustLDAP { ldap_ptr: *mut LDAP, } + unsafe impl Sync for RustLDAP {} unsafe impl Send for RustLDAP {} @@ -177,8 +172,8 @@ impl RustLDAP { /// Create a new `RustLDAP`. /// /// Creates a new `RustLDAP` and initializes underlying `OpenLDAP` library. Upon creation, a - /// subsequent calls to `set_option` and `simple_bind_authentication` are possible. Before calling a search - /// related function, one must bind to the server by calling `simple_bind_authentication`. See module usage + /// subsequent calls to `set_option` and `simple_bind` are possible. Before calling a search + /// related function, one must bind to the server by calling `simple_bind`. See module usage /// information for more details on using a `RustLDAP` object. /// /// # Parameters @@ -186,6 +181,7 @@ 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(); @@ -195,10 +191,12 @@ 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 }) @@ -246,9 +244,10 @@ 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) } @@ -264,17 +263,15 @@ 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 @@ -300,11 +297,7 @@ 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(), @@ -320,9 +313,10 @@ 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) @@ -346,18 +340,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(); @@ -401,24 +395,23 @@ 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())); } } @@ -428,6 +421,7 @@ 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(); @@ -436,18 +430,17 @@ 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(); @@ -458,8 +451,7 @@ 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) @@ -481,10 +473,12 @@ 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. @@ -497,8 +491,8 @@ impl RustLDAP { #[cfg(test)] mod tests { - use codes; use std::ptr; + use codes; const TEST_ADDRESS: &'static str = "ldap://ldap.forumsys.com"; const TEST_BIND_DN: &'static str = "cn=read-only-admin,dc=example,dc=com"; @@ -518,12 +512,9 @@ 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); } @@ -542,6 +533,7 @@ 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] @@ -567,18 +559,20 @@ 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); @@ -595,27 +589,26 @@ 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 @@ -633,53 +626,51 @@ 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 @@ -694,28 +685,27 @@ 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 { @@ -727,5 +717,7 @@ mod tests { } } } + } + } From 00d361d1cbcadf339b117bd5db6fa95d85b15733 Mon Sep 17 00:00:00 2001 From: Mathias Myrland Date: Sun, 12 Apr 2020 11:56:18 +0200 Subject: [PATCH 3/8] (docker-based-examples) #9 Added function to escape filter strings --- src/lib.rs | 365 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 214 insertions(+), 151 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d0b1406..5739c4a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,13 +4,15 @@ //! LDAP directory. //! extern crate libc; -use libc::{c_int, c_char, c_void, timeval}; +use errors::LDAPError; +use libc::{c_char, c_int, c_void, timeval}; use std::collections::HashMap; use std::ffi::{CStr, CString}; +use std::iter::{FlatMap, FromIterator}; use std::ptr; -use std::slice; -use std::boxed; use std::ptr::null_mut; +use std::slice; +use std::{ascii, boxed}; pub mod codes; pub mod errors; @@ -47,41 +49,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 +100,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,7 +113,6 @@ pub struct RustLDAP { ldap_ptr: *mut LDAP, } - unsafe impl Sync for RustLDAP {} unsafe impl Send 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,49 @@ 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) -> Result { + String::from_utf8( + input + .escape_default() + .to_string() + .bytes() + .flat_map(|c| match c { + b'\0' => vec![b'\\', b'0', b'0'], + 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'], + _ => vec![c], + }) + .collect(), + ) + .map_err(|e| LDAPError::NativeError("Error while escaping filter argument".into())) +} + #[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 +558,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 +582,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] @@ -559,20 +607,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 +635,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 +673,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 +734,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 +767,20 @@ mod tests { } } } - } + #[test] + fn test_search_filter_escapation() { + let input_a = + r"*\doesnotmatter)(isMemberOf=cn=admins,ou=groups,ou=example,ou=org)(doesnotmatter="; + let expected_filtered_a = r"\2a\5c\5cdoesnotmatter\29\28isMemberOf=cn=admins,ou=groups,ou=example,ou=org\29\28doesnotmatter="; + + let input_b = "thisshouldnotbealtered...[][]---,;;;"; + + let filtered_input_a = escape_filter_assertion_value(input_a).unwrap(); + let filtered_input_b = escape_filter_assertion_value(input_b).unwrap(); + + assert_eq!(expected_filtered_a, filtered_input_a); + assert_eq!(input_b, filtered_input_b); + } } From bc2c2122f913ddf27a13dc22e60ba114b6ba9d4e Mon Sep 17 00:00:00 2001 From: Mathias Myrland Date: Sun, 12 Apr 2020 12:05:18 +0200 Subject: [PATCH 4/8] (docker-based-examples) #9 Updated example to use filter escaping, and to protect against timing attacks for user discovery --- .../simple_bind_authentication/Cargo.toml | 11 +++++++++++ .../simple_bind_authentication/src/main.rs | 19 +++++++++++++------ 2 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 examples/simple_bind_authentication/Cargo.toml 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/src/main.rs b/examples/simple_bind_authentication/src/main.rs index 4cf16e3..2725776 100644 --- a/examples/simple_bind_authentication/src/main.rs +++ b/examples/simple_bind_authentication/src/main.rs @@ -62,6 +62,9 @@ fn do_simple_bind( } 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 @@ -69,7 +72,7 @@ fn ldap_dn_lookup(ldap: &RustLDAP, who: &str) -> Result { // // This particular filter allows the user to sign in with either // uid or email - let filter = format!("(|(uid={})(mail={}))", who, who); + let filter = format!("(|(uid={})(mail={}))", safe_who, safe_who); match ldap.ldap_search( "ou=people,dc=planetexpress,dc=com", @@ -122,11 +125,15 @@ fn main() { // In our test scenario, the professor is the manager. do_simple_bind(&ldap, ldap_manager_dn, ldap_manager_pass).unwrap(); - if let Ok(fry_dn) = ldap_dn_lookup(&ldap, user_to_authenticate.as_str()) { - // Now, perform a bind with the DN we found matching the user attempting to sign in - // and the password provided in the authentication request - do_simple_bind(&ldap, fry_dn.as_str(), pwd_to_authenticate.as_str()).unwrap(); + let (dn, passwd, valid) = match ldap_dn_lookup(&ldap, user_to_authenticate.as_str()) { + Ok(fry_dn) => (fry_dn, pwd_to_authenticate, true), + _ => ("".into(), "".into(), false), + }; - println!("Successfully signed in as fry"); + // 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 fry"), + false => println!("Could not log in"), } } From 3591f2d6b0e7b4dcb1c94d79796df5571ed2b177 Mon Sep 17 00:00:00 2001 From: Mathias Myrland Date: Sun, 12 Apr 2020 12:08:29 +0200 Subject: [PATCH 5/8] (docker-based-examples) cargo fix, cleanup --- src/errors.rs | 7 +++---- src/lib.rs | 8 +++----- 2 files changed, 6 insertions(+), 9 deletions(-) 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 5739c4a..74a8530 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,13 +6,11 @@ extern crate libc; use errors::LDAPError; use libc::{c_char, c_int, c_void, timeval}; +use std::boxed; use std::collections::HashMap; use std::ffi::{CStr, CString}; -use std::iter::{FlatMap, FromIterator}; use std::ptr; -use std::ptr::null_mut; use std::slice; -use std::{ascii, boxed}; pub mod codes; pub mod errors; @@ -531,7 +529,7 @@ pub fn escape_filter_assertion_value(input: &str) -> Result { }) .collect(), ) - .map_err(|e| LDAPError::NativeError("Error while escaping filter argument".into())) + .map_err(|_e| LDAPError::NativeError("Error while escaping filter argument".into())) } #[cfg(test)] @@ -590,7 +588,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, From 4cd6f922a797b5ab27ee7f63f30a53169042f9c3 Mon Sep 17 00:00:00 2001 From: Mathias Myrland Date: Sun, 12 Apr 2020 12:20:33 +0200 Subject: [PATCH 6/8] (docker-based-examples) updted example text --- examples/simple_bind_authentication/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/simple_bind_authentication/src/main.rs b/examples/simple_bind_authentication/src/main.rs index 2725776..510f5da 100644 --- a/examples/simple_bind_authentication/src/main.rs +++ b/examples/simple_bind_authentication/src/main.rs @@ -126,14 +126,14 @@ fn main() { 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(fry_dn) => (fry_dn, pwd_to_authenticate, true), + 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 fry"), + true => println!("Successfully signed in as {}", dn), false => println!("Could not log in"), } } From 36c3861fb0a9f227d3c2c507708fcb9fd56c7bd0 Mon Sep 17 00:00:00 2001 From: Mathias Myrland Date: Sun, 12 Apr 2020 17:09:34 +0200 Subject: [PATCH 7/8] (docker-based-examples) #9 removed default encoding, added missing cases --- src/lib.rs | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 74a8530..075cb4d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -514,17 +514,28 @@ impl RustLDAP { /// } /// ``` pub fn escape_filter_assertion_value(input: &str) -> Result { + fn to_hex(byte: u8) -> u8 { + match byte { + 0..=9 => b'0' + byte, + _ => b'a' + byte - 10, + } + } + String::from_utf8( input - .escape_default() - .to_string() .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(), @@ -769,15 +780,17 @@ mod tests { #[test] fn test_search_filter_escapation() { - let input_a = - r"*\doesnotmatter)(isMemberOf=cn=admins,ou=groups,ou=example,ou=org)(doesnotmatter="; - let expected_filtered_a = r"\2a\5c\5cdoesnotmatter\29\28isMemberOf=cn=admins,ou=groups,ou=example,ou=org\29\28doesnotmatter="; + 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 = escape_filter_assertion_value(input_a).unwrap(); let filtered_input_b = escape_filter_assertion_value(input_b).unwrap(); - + assert_eq!( + 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); } From 9eb020431433bfb9ca951dd6d2476aff42ea28c3 Mon Sep 17 00:00:00 2001 From: Mathias Myrland Date: Sun, 12 Apr 2020 20:27:58 +0200 Subject: [PATCH 8/8] (docker-based-examples) removed to_utf_8 call from escape function --- src/lib.rs | 50 ++++++++++++++++++++++++-------------------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 075cb4d..3785816 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,7 @@ //! LDAP directory. //! extern crate libc; -use errors::LDAPError; + use libc::{c_char, c_int, c_void, timeval}; use std::boxed; use std::collections::HashMap; @@ -28,6 +28,7 @@ pub struct LDAPControl; struct BerElement; unsafe impl Sync for LDAP {} + unsafe impl Send for LDAP {} #[link(name = "lber")] @@ -112,6 +113,7 @@ pub struct RustLDAP { } unsafe impl Sync for RustLDAP {} + unsafe impl Send for RustLDAP {} impl Drop for RustLDAP { @@ -513,7 +515,7 @@ impl RustLDAP { /// 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) -> Result { +pub fn escape_filter_assertion_value(input: &str) -> Vec { fn to_hex(byte: u8) -> u8 { match byte { 0..=9 => b'0' + byte, @@ -521,31 +523,27 @@ pub fn escape_filter_assertion_value(input: &str) -> Result { } } - String::from_utf8( - 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(), - ) - .map_err(|_e| LDAPError::NativeError("Error while escaping filter argument".into())) + 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, escape_filter_assertion_value}; @@ -785,10 +783,10 @@ mod tests { let input_b = "thisshouldnotbealtered...[][]---,;;;"; - let filtered_input_a = escape_filter_assertion_value(input_a).unwrap(); - let filtered_input_b = escape_filter_assertion_value(input_b).unwrap(); + 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!( - escape_filter_assertion_value("🌏").unwrap(), + String::from_utf8(escape_filter_assertion_value("🌏")).unwrap(), r"\f0\9f\8c\8f", ); assert_eq!(expected_filtered_a, filtered_input_a);