From 25215308c31789b15b50ae57b2f08c64e8640ab4 Mon Sep 17 00:00:00 2001 From: Josh Leverette Date: Mon, 8 Aug 2016 21:01:38 -0500 Subject: [PATCH 01/22] Updated README and metadata. --- Cargo.toml | 12 ++++++------ README.md | 4 +++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 05a3495..d8a266c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,12 @@ [package] -name = "cldap" -version = "0.1.0" -authors = ["Ross Delinger "] +name = "openldap" +version = "1.0.0" +authors = ["Josh Leverette ", "Ross Delinger "] license = "MIT" readme = "README.md" -repository = "https://github.com/rossdylan/rust-cldap" -homepage = "https://github.com/rossdylan/rust-cldap" -description = "Rust bindings to the C ldap library." +repository = "https://github.com/coder543/rust-cldap" +homepage = "https://github.com/coder543/rust-cldap" +description = "Straightforward Rust bindings to the C openldap library." [dependencies] libc = "0.2.10" diff --git a/README.md b/README.md index 23c9e24..133b595 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ cldap ===== -Rust bindings for the C LDAP library. Provides a simple public API for querying +Rust bindings for the C openldap library. Provides a simple public API for querying ldap. + +Right now, the best place to find usage examples is in src/lib.rs in the `mod tests` section at the bottom. From 991ffb4d1e8a7266a8f7119fd56bc24ff92b1dce Mon Sep 17 00:00:00 2001 From: Josh Leverette Date: Mon, 8 Aug 2016 21:04:30 -0500 Subject: [PATCH 02/22] Updated metadata. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d8a266c..256af1b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" readme = "README.md" repository = "https://github.com/coder543/rust-cldap" homepage = "https://github.com/coder543/rust-cldap" -description = "Straightforward Rust bindings to the C openldap library." +description = "Straightforward Rust bindings to the C openldap library. This is a fork of cldap that has been methodically fixed, extended, and made to be more compliant with openldap. It should be relatively robust and production ready at this point." [dependencies] libc = "0.2.10" From cb87c3b1fdac741b97cc10f486d0b3e7f8412ab5 Mon Sep 17 00:00:00 2001 From: Josh Leverette Date: Mon, 15 Aug 2016 11:17:15 -0500 Subject: [PATCH 03/22] make the LDAP object thread safe --- Cargo.toml | 2 +- src/lib.rs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 256af1b..9831a67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "openldap" -version = "1.0.0" +version = "1.0.1" authors = ["Josh Leverette ", "Ross Delinger "] license = "MIT" readme = "README.md" diff --git a/src/lib.rs b/src/lib.rs index ca32e5e..4aba741 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,13 +17,16 @@ pub struct LDAPControl; #[repr(C)] struct BerElement; +unsafe impl Sync for LDAP {} +unsafe impl Send for LDAP {} + #[link(name = "lber")] #[allow(improper_ctypes)] extern { fn ber_free(ber: *const BerElement, freebuf: c_int); } -#[link(name = "ldap")] +#[link(name = "ldap_r")] #[allow(improper_ctypes)] extern { fn ldap_initialize(ldap: *mut *mut LDAP, uri: *const c_char) -> c_int; From 083f939cca429472d6087d04d5b757d153c4846d Mon Sep 17 00:00:00 2001 From: Josh Leverette Date: Mon, 15 Aug 2016 11:23:39 -0500 Subject: [PATCH 04/22] impl Sync + Send for RustLDAP --- src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 4aba741..18be0d8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,6 +56,10 @@ pub struct RustLDAP { ldap_ptr: *mut LDAP } + +unsafe impl Sync for RustLDAP {} +unsafe impl Send for RustLDAP {} + impl Drop for RustLDAP { fn drop(&mut self){ From a4bf520da855e7f9163f2293df36d0e8adb6ef53 Mon Sep 17 00:00:00 2001 From: Josh Leverette Date: Tue, 6 Sep 2016 10:35:25 -0500 Subject: [PATCH 05/22] Overhaul rust-cldap (#1) * Added additional LDAP constants/codes. Added LDAP options that allow for TLS settings to be configured. * Add a LDAPError abstraction. The current implementation doesn't allow for nice error handling using the try! macro. This change positions us to refactor the RustLDAP library to return LDAPError, which implement the traits needed to use try!, among other things. Currently, all errors are naively packed as a generic error, but it sets us up to do more fine grained error handling, so users can handle errors intelligently. Be warned: this major change is a change to the public API of the library. If we're using semantic versioning, this should be a major + 1. * Add RustLDAP.set_option method. This change gives us the ability to set options on the LDAP connection. Additionally, I've cleaned up documentation in various places, and added documentation where it was missing. * s/\t/ /g Replace tabs with spaces. * Add travis.yml. * Improve the README.md. This is a library, so this tries to make the docs a little friendlier. * Empty commit for build. * Describe search return type in README.md. * Modify shield url. Per travis-ci.org docs, this is the new suggested link. * Turn off sudo. * Change example code slightly. * Simplifying .travis.yml. Builds aren't getting picked up for some reason. Checking to see if travis is messed up. * Empty commit for build. * Empty commit for travis-ci.org. * Don't use std::mem::transmute. Get rid of this scary function by using heap allocated memory. We still do unsafe things using boxes, but this is a more idiomatic approach that should be memory safe, per the docs for CString and Box, linked below. In a nutshell, T::into_raw functions transfer ownership of the raw pointer to the caller, thereby protecting us from Rust's ownership system freeing the pointer before we use it. The caller cleans up the memory using T::from_raw later on. CString::into_raw - https://doc.rust-lang.org/std/ffi/struct.CString.html#method.into_raw Box::into_raw - https://doc.rust-lang.org/std/boxed/struct.Box.html#method.into_raw * Punctuation correction. --- .gitignore | 1 + .travis.yml | 4 + README.md | 48 ++- src/codes.rs | 16 + src/errors.rs | 54 ++++ src/lib.rs | 874 +++++++++++++++++++++++++++++--------------------- 6 files changed, 628 insertions(+), 369 deletions(-) create mode 100644 .travis.yml create mode 100644 src/errors.rs diff --git a/.gitignore b/.gitignore index a9d37c5..d4f917d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ target Cargo.lock +*.swp diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..23d4db7 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,4 @@ +language: rust +sudo: false +rust: + - stable diff --git a/README.md b/README.md index 133b595..2fb592a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,45 @@ -cldap -===== +# openldap -Rust bindings for the C openldap library. Provides a simple public API for querying -ldap. +[![Build Status](https://travis-ci.org/sholsapp/rust-cldap.svg?branch=master)](https://travis-ci.org/sholsapp/rust-cldap) -Right now, the best place to find usage examples is in src/lib.rs in the `mod tests` section at the bottom. +Rust bindings for the native OpenLDAP library with a few convenient +abstractions for connecting, binding, configuring, and querying your LDAP +server. + +## usage + +Using openldap is as easy as the following. + +``` +static LDAP_URI: &'static str = "ldaps://localhost:636"; + +static LDAP_USER: &'static str = "user"; + +static LDAP_PASS: &'static str = "pass"; + +fn main() { + let ldap = try!(RustLDAP::new(LDAP_URI)); + + ldap.set_option(codes::options::LDAP_OPT_PROTOCOL_VERSION, + &codes::versions::LDAP_VERSION3); + + ldap.set_option(codes::options::LDAP_OPT_X_TLS_REQUIRE_CERT, + &codes::options::LDAP_OPT_X_TLS_DEMAND); + + try!(ldap.simple_bind(LDAP_USER, LDAP_PASS)); + + // Returns a LDAPResponse, a.k.a. Vec>>. + let res = ldap.simple_search( + "CN=Stephen,OU=People,DC=Earth", + codes::scopes::LDAP_SCOPE_BASE, + ).unwrap(); +} +``` + +When performing an operation that can fail, use the `try!` macro. On failure, +an `openldap::errors::LDAPError` will be returned that includes a detailed +message from the native OpenLDAP library. + +## developers + +To author new changes to this library, do XYZ. diff --git a/src/codes.rs b/src/codes.rs index 7d54fd4..3cd232c 100644 --- a/src/codes.rs +++ b/src/codes.rs @@ -1,3 +1,6 @@ +//! LDAP codes from transcribed from ldap.h. +//! + /// Re-export protocol result codes from ldap.h pub mod results { pub static LDAP_SUCCESS: i32 = 0x00; @@ -148,4 +151,17 @@ pub mod options { pub static LDAP_OPT_SECURITY_CONTEXT: i32 = 0x0099; pub static LDAP_OPT_API_EXTENSION_BASE: i32 = 0x4000; + + pub static LDAP_OPT_X_TLS_CACERTDIR: i32 = 0x6003; + pub static LDAP_OPT_X_TLS_CACERTFILE: i32 = 0x6002; + pub static LDAP_OPT_X_TLS_CERTFILE: i32 = 0x6004; + pub static LDAP_OPT_X_TLS_KEYFILE: i32 = 0x6005; + pub static LDAP_OPT_X_TLS_NEWCTX: i32 = 0x600f; + pub static LDAP_OPT_X_TLS_REQUIRE_CERT: i32 = 0x6006; + + pub static LDAP_OPT_X_TLS_NEVER: i32 = 0x0000; + pub static LDAP_OPT_X_TLS_HARD: i32 = 0x0001; + pub static LDAP_OPT_X_TLS_DEMAND: i32 = 0x0002; + pub static LDAP_OPT_X_TLS_ALLOW: i32 = 0x0003; + pub static LDAP_OPT_X_TLS_TRY: i32 = 0x0004; } diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..88ee783 --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,54 @@ +//! Errors and trait implementations. +//! +use std::fmt; +use std::error; +use std::convert; + + +/// A LDAP error. +/// +/// LDAP errors occur when an underlying function returns with an error code. Currently, there is +/// only one type of error raised: `LDAPError::NativeError`. A `LDAPError::NativeError` includes a +/// string field describing the error in more detail. +/// +/// A `LDAPError` implements necessary traits (i.e., std::fmt::Display, std::error::Error, and +/// std::convert::From) to do proper error handling using the `try!` macro. +/// +#[derive(Debug, PartialEq)] +pub enum LDAPError { + NativeError(String), +} + +impl fmt::Display for LDAPError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + LDAPError::NativeError(ref err) => write!(f, "LDAP error: {}", err), + } + } +} + +impl error::Error for LDAPError { + + /// Get the description of this error. + /// + fn description(&self) -> &str { + match *self { + LDAPError::NativeError(ref err) => err + } + } + + /// Get the cause of this error. + /// + /// Note, currently this method always return `None` as we do not know the root cause of the + /// error. + fn cause(&self) -> Option<&error::Error> { + None + } +} + +impl convert::From for LDAPError { + fn from(err: String) -> LDAPError { + LDAPError::NativeError(err) + } +} + diff --git a/src/lib.rs b/src/lib.rs index 4aba741..a9e528a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,19 +1,28 @@ +//! Objects for connecting and querying LDAP servers using OpenLDAP. +//! +//! Current support includes connection, initializing, binding, configuring, and search against an +//! LDAP directory. +//! extern crate libc; use libc::{c_int, c_char, c_void, timeval}; -use std::ptr; -use std::ffi::{CStr, CString}; use std::collections::HashMap; +use std::ffi::{CStr, CString}; +use std::ptr; use std::slice; +use std::boxed; pub mod codes; +pub mod errors; -// Define the structs used by the ldap c library. #[repr(C)] struct LDAP; + #[repr(C)] struct LDAPMessage; + #[repr(C)] pub struct LDAPControl; + #[repr(C)] struct BerElement; @@ -23,389 +32,526 @@ unsafe impl Send for LDAP {} #[link(name = "lber")] #[allow(improper_ctypes)] extern { - fn ber_free(ber: *const BerElement, freebuf: c_int); + fn ber_free(ber: *const BerElement, freebuf: c_int); } #[link(name = "ldap_r")] #[allow(improper_ctypes)] extern { - fn ldap_initialize(ldap: *mut *mut LDAP, uri: *const c_char) -> c_int; - fn ldap_memfree(p: *mut c_void); - fn ldap_msgfree(msg: *mut LDAPMessage) -> c_int; - fn ldap_err2string(err: c_int) -> *const c_char; - 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_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_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; + static ber_pvt_opt_on: c_char; + fn ldap_initialize(ldap: *mut *mut LDAP, uri: *const c_char) -> c_int; + fn ldap_memfree(p: *mut c_void); + fn ldap_msgfree(msg: *mut LDAPMessage) -> c_int; + fn ldap_err2string(err: c_int) -> *const c_char; + 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_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; } +/// A typedef for an LDAPResponse type. +/// +/// LDAP responses are organized as vectors of mached entities. Typically, each entity is +/// represented as a map of attributes to list of values. +/// +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 +/// created, configured, and queried. Methods that call underlying OpenLDAP calls that can fail +/// will raise an `errors::LDAPError` with additional details. +/// +/// Using a `RustLDAP` object is easy! +/// pub struct RustLDAP { - // Have the raw pointer to it so we can pass it into internal functions - ldap_ptr: *mut LDAP + /// A pointer to the underlying OpenLDAP object. + ldap_ptr: *mut LDAP, } impl Drop for RustLDAP { + fn drop(&mut self){ + // Unbind the LDAP connection, making the C library free the LDAP*. + let rc = unsafe { ldap_unbind_ext_s(self.ldap_ptr, ptr::null_mut(), ptr::null_mut()) }; + + // Make sure it actually happened. + if rc != codes::results::LDAP_SUCCESS { + unsafe { + // Hopefully this never happens. + let raw_estr = ldap_err2string(rc as c_int); + panic!(CStr::from_ptr(raw_estr).to_owned().into_string().unwrap()); + } + } + } +} - fn drop(&mut self){ - - //unbind the LDAP connection, making the C library free the LDAP* - let rc = unsafe { ldap_unbind_ext_s(self.ldap_ptr, ptr::null_mut(), ptr::null_mut()) }; - - //make sure it actually happened - if rc != codes::results::LDAP_SUCCESS { - unsafe { //hopefully this never happens - let raw_estr = ldap_err2string(rc as c_int); - panic!(CStr::from_ptr(raw_estr).to_owned().into_string().unwrap()); - } +/// A trait for types that can be passed as LDAP option values. +/// +/// Underlying OpenLDAP implementation calls for option values to be passed in as *const c_void, +/// while allowing values to be i32 or string. Using traits, we implement function overloading to +/// handle i32 and string option value types. +/// +/// This trait allocates memory that a caller must free using `std::boxed::Box::from_raw`. This +/// helps guarantee that there is not a use after free bug (in Rust) while providing the appearance +/// of opaque memory to OpenLDAP (in C). In pure C, we would've accomplished this by casting a +/// local variable to a `const void *`. In Rust, we must do this on the heap to ensure Rust's +/// ownership system does not free the memory used to store the option value between now and when +/// the option is actually set. +/// +pub trait LDAPOptionValue { + fn as_cvoid_ptr(&self) -> *const c_void; +} - } +impl LDAPOptionValue for str { + fn as_cvoid_ptr(&self) -> *const c_void { + let string = CString::new(self).unwrap(); + string.into_raw() as *const c_void + } +} - } +impl LDAPOptionValue for i32 { + fn as_cvoid_ptr(&self) -> *const c_void { + let mem = boxed::Box::new(*self); + boxed::Box::into_raw(mem) as *const c_void + } +} +impl LDAPOptionValue for bool { + fn as_cvoid_ptr(&self) -> *const c_void { + match *self { + true => { + let mem = boxed::Box::new(&ber_pvt_opt_on); + boxed::Box::into_raw(mem) as *const c_void + }, + false => { + let mem = boxed::Box::new(0); + boxed::Box::into_raw(mem) as *const c_void + } + } + } } impl RustLDAP { - /// Create a new RustLDAP struct and use an ffi call to ldap_initialize to - /// allocate and init a C LDAP struct. All of that is hidden inside of RustLDAP. - pub fn new(uri: &str) -> Result { - - //attempt to convert the URI string into a C-string - let uri_cstring = CString::new(uri).unwrap(); - - //Create some space for the LDAP* - let mut cldap = ptr::null_mut(); - - unsafe { - //call ldap_initialize and check for errors - 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(CStr::from_ptr(raw_estr).to_owned().into_string().unwrap()); - } - - } - - //create and return a new instance - let new_ldap = RustLDAP { ldap_ptr: cldap }; - return Ok(new_ldap); - } - - /// Perform a synchronos simple bind (ldap_simple_bind_s). The result is - /// either Ok(LDAP_SUCCESS) or Err(ldap_err2string). - pub fn simple_bind(&self, who: &str, pass: &str) -> Result { - - //convert arguments to C-strings - let who_cstr = CString::new(who).unwrap(); - let pass_cstr = CString::new(pass).unwrap(); - let who_ptr = who_cstr.as_ptr(); - let pass_ptr = pass_cstr.as_ptr(); - - //call ldap_bind and check for errors - unsafe { - 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(CStr::from_ptr(raw_estr).to_owned().into_string().unwrap()); - } - return Ok(res); - } - } - - /// Perform a simple search with only the base, returning all attributes found - pub fn simple_search(&self, base: &str, scope: i32) -> Result>>, String> { - return self.ldap_search(base, scope, None, None, false, None, None, ptr::null_mut(), -1); - } - - /// Expose a not very 'rust-y' api for ldap_search_ext_s. Ideally this will - /// be used mainly internally and a simpler api is exposed to users. - 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>>, String> { - - //Make room for the LDAPMessage, being sure to delete this before we return - let mut ldap_msg = ptr::null_mut();; - - //Convert the passed in filter sting to either a C-string or null if one is not passed - let filter_cstr: CString; - let r_filter = match filter { - Some(fs) => { - filter_cstr = CString::new(fs).unwrap(); - filter_cstr.as_ptr() - }, - None => ptr::null() - }; - - //Convert the vec of attributes into the null-terminated array that the library expects - //We also copy the strings into C-strings - let mut r_attrs: *const *const c_char = ptr::null(); - let mut c_strs: Vec = Vec::new(); - let mut r_attrs_ptrs: Vec<*const c_char> = Vec::new(); - - if let Some(strs) = attrs { - for string in strs { - - //create new CString and take ownership of it in c_strs - c_strs.push(CString::new(string).unwrap()); - - //create a pointer to that CString's raw data and store it in r_attrs - r_attrs_ptrs.push(c_strs[c_strs.len() - 1].as_ptr()); - } - r_attrs_ptrs.push(ptr::null()); //ensure that there is a null value at the end of the vec - r_attrs = r_attrs_ptrs.as_ptr(); - } - - //PAss in either the controlls or a null if none are specified - let r_serverctrls = match serverctrls { - Some(sc) => sc, - None => ptr::null_mut() - }; - let r_clientctrls = match clientctrls { - Some(cc) => cc, - None => ptr::null_mut() - }; - - //Copy the search base into a C-string - let base = CString::new(base).unwrap(); - - //call into the C library and check for error - 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); - if res != codes::results::LDAP_SUCCESS { - let raw_estr = ldap_err2string(res as c_int); - return Err(CStr::from_ptr(raw_estr).to_owned().into_string().unwrap()); - } - } - - //We now have to parse the results, copying the C-strings into Rust ones - //making sure to free the C-strings afterwards - let mut resvec: Vec>> = vec![]; - 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(); - let mut ber: *mut BerElement = ptr::null_mut(); - unsafe { - 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(); - - //get the attribute values from LDAP - let raw_vals: *const *const c_char = ldap_get_values(self.ldap_ptr, entry, attr); - let raw_vals_len = ldap_count_values(raw_vals) as usize; - let val_slice: &[*const c_char] = slice::from_raw_parts(raw_vals, raw_vals_len); - - //map these into a vec of Strings - let values: Vec = val_slice.iter().map(|ptr| { - CStr::from_ptr(*ptr).to_owned().into_string().unwrap() - }).collect(); - - //insert newly constructed Rust key-value strings - map.insert(key, values); - - //free the attr and value, then get next attr - ldap_value_free(raw_vals); - ldap_memfree(attr as *mut c_void); - attr = ldap_next_attribute(self.ldap_ptr, entry, ber) - - } - - //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 vec - resvec.push(map); - - } - - //make sure we free the message and return the parsed results - unsafe { ldap_msgfree(ldap_msg) }; - return Ok(resvec); - } + /// Creat 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 + /// information for more details on using a RustLDAP object. + /// + /// # Parameters + /// + /// * 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(); + + let uri_cstring = CString::new(uri).unwrap(); + + unsafe { + 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())); + } + + } + + let new_ldap = RustLDAP { + ldap_ptr: cldap, + }; + return Ok(new_ldap); + } + + /// Sets an option on the LDAP connection. + /// + /// When setting an option to _ON_ or _OFF_ one may use the boolean values `true` or `false`, + /// respectively. + /// + /// # Parameters + /// + /// * option - An option identifier from `cldap::codes`. + /// * value - The value to set for the option. + /// + pub fn set_option(&self, option: i32, value: &T) -> bool { + let ptr: *const c_void = value.as_cvoid_ptr(); + unsafe { + let res: i32; + res = ldap_set_option( + self.ldap_ptr, + option, + ptr, + ); + // Allows for memory to be dropped when this binding goes away. + let _ = boxed::Box::from_raw(ptr as *mut c_void); + return res == 0 + } + + } + + /// Bind to the LDAP server. + /// + /// If you wish to configure options on the LDAP server, be sure to set required options using + ///`set_option` _before_ binding to the LDAP server. In some advanced cases, it may be required + /// to set multiple options for an option to be made available. Refer to the OpenLDAP + /// documentation for information on available options and how to use them. + /// + /// # Parameters + /// + /// * who - The user's name to bind with. + /// * pass - The user's password to bind with. + /// + pub fn simple_bind(&self, who: &str, pass: &str) -> Result { + let who_cstr = CString::new(who).unwrap(); + let pass_cstr = CString::new(pass).unwrap(); + let who_ptr = who_cstr.as_ptr(); + let pass_ptr = pass_cstr.as_ptr(); + unsafe { + 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 Ok(res); + } + } + + /// Simple synchronous search. + /// + /// Performs a simple search with only the base, returning all attributes found. + /// + /// # Parameters + /// + /// * base - The LDAP base. + /// * scope - The search scope. See `cldap::codes::scopes`. + /// + pub fn simple_search(&self, base: &str, scope: i32) -> Result { + return self.ldap_search( + base, + scope, + None, + None, + false, + None, + None, + ptr::null_mut(), + -1, + ); + } + + /// Advanced synchronous search. + /// + /// Exposes a raw API around the underlying `ldap_search_ext_s` function from OpenLDAP. + /// Wherever possible, use provided wrappers. + /// + /// # Parameters + /// + /// * base - The base domain. + /// * scope - The search scope. See `cldap::codes::scopes`. + /// * filter - An optional filter. + /// * attrs - An optional set of attrs. + /// * attrsonly - True if should return only the attrs specified in `attrs`. + /// * serverctrls - Optional sever controls. + /// * clientctrls - Optional client controls. + /// * 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 { + + // Make room for the LDAPMessage, being sure to delete this before we return. + let mut ldap_msg = ptr::null_mut();; + + // Convert the passed in filter sting to either a C-string or null if one is not passed. + let filter_cstr: CString; + let r_filter = match filter { + Some(fs) => { + filter_cstr = CString::new(fs).unwrap(); + filter_cstr.as_ptr() + }, + None => ptr::null() + }; + + // Convert the vec of attributes into the null-terminated array that the library expects. + let mut r_attrs: *const *const c_char = ptr::null(); + let mut c_strs: Vec = Vec::new(); + let mut r_attrs_ptrs: Vec<*const c_char> = Vec::new(); + + if let Some(strs) = attrs { + for string in strs { + // Create new CString and take ownership of it in c_strs. + c_strs.push(CString::new(string).unwrap()); + // Create a pointer to that CString's raw data and store it in r_attrs. + r_attrs_ptrs.push(c_strs[c_strs.len() - 1].as_ptr()); + } + // Ensure that there is a null value at the end of the vector. + r_attrs_ptrs.push(ptr::null()); + r_attrs = r_attrs_ptrs.as_ptr(); + } + + let r_serverctrls = match serverctrls { + Some(sc) => sc, + None => ptr::null_mut() + }; + + let r_clientctrls = match clientctrls { + Some(cc) => cc, + None => ptr::null_mut() + }; + + 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, + ); + 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())); + } + } + + // We now have to parse the results, copying the C-strings into Rust ones making sure to + // free the C-strings afterwards + let mut resvec: Vec>> = vec![]; + 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(); + let mut ber: *mut BerElement = ptr::null_mut(); + unsafe { + 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(); + + // Get the attribute values from LDAP. + let raw_vals: *const *const c_char = ldap_get_values(self.ldap_ptr, entry, attr); + let raw_vals_len = ldap_count_values(raw_vals) as usize; + 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().map(|ptr| { + // TODO(sholsapp): If this contains binary data this will fail. + CStr::from_ptr(*ptr).to_owned().into_string().unwrap_or("".to_string()) + }).collect(); + + // Insert newly constructed Rust key-value strings. + map.insert(key, values); + + // Free the attr and value, then get next attr. + ldap_value_free(raw_vals); + ldap_memfree(attr as *mut c_void); + attr = ldap_next_attribute(self.ldap_ptr, entry, ber) + + } + + // 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. + unsafe { ldap_msgfree(ldap_msg) }; + return Ok(resvec); + } } #[cfg(test)] mod tests { - 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"; - const TEST_BIND_PASS: &'static str = "password"; - const TEST_SIMPLE_SEARCH_QUERY: &'static str = "uid=tesla,dc=example,dc=com"; - const TEST_SEARCH_BASE: &'static str = "dc=example,dc=com"; - const TEST_SEARCH_FILTER: &'static str = "(uid=euler)"; - const TEST_SEARCH_INVALID_FILTER: &'static str = "(uid=INVALID)"; - - /// Test creating a RustLDAP struct with a valid uri. - #[test] - fn test_ldap_new(){ - - let _ = super::RustLDAP::new(TEST_ADDRESS).unwrap(); - - } - - /// Test creating a RustLDAP struct with an invalid uri. - #[test] - fn test_invalid_ldap_new(){ - - if let Err(e) = super::RustLDAP::new("lda://localhost"){ - - assert_eq!("Bad parameter to an ldap routine", e); - - } else { - - assert!(false); - - } - - } - - #[test] - #[should_panic] - fn test_invalid_cstring_ldap_new(){ - - let _ = super::RustLDAP::new("INVALID\0CSTRING").unwrap(); - - } - - #[test] - fn test_simple_bind(){ - - let ldap = super::RustLDAP::new(TEST_ADDRESS).unwrap(); - let res = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).unwrap(); - println!("Bind result: {:?}", res); - - } - - #[test] - fn test_simple_search(){ - - println!("Testing simple search"); - let ldap = super::RustLDAP::new(TEST_ADDRESS).unwrap(); - let _ = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).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); - - for result in search_res { - println!("simple search result: {:?}", result); - for (key, value) in result { - println!("- key: {:?}", key); - for res_val in value { - println!("- - res_val: {:?}", res_val); - } - } - } - - } - - #[test] - fn test_search(){ - - println!("Testing search"); - let ldap = super::RustLDAP::new(TEST_ADDRESS).unwrap(); - let _ = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).unwrap(); - 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 - assert!(search_res.len() == 1); - - for result in search_res { - println!("search result: {:?}", result); - for (key, value) in result { - println!("- key: {:?}", key); - for res_val in value { - println!("- - res_val: {:?}", res_val); - } - } - } - - } - - #[test] - fn test_invalid_search(){ - - println!("Testing invalid search"); - let ldap = super::RustLDAP::new(TEST_ADDRESS).unwrap(); - let _ = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).unwrap(); - 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(); - let _ = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).unwrap(); - 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 - assert!(search_res.len() == 1); - - for result in search_res { - println!("attrs search result: {:?}", result); - for (key, value) in result { - println!("- key: {:?}", key); - for res_val in value { - println!("- - res_val: {:?}", res_val); - } - } - } - - } - - #[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(); - let _ = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).unwrap(); - 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 { - println!("attrs search result: {:?}", result); - for (key, value) in result { - println!("- key: {:?}", key); - for res_val in value { - println!("- - res_val: {:?}", res_val); - } - } - } - - } + 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"; + const TEST_BIND_PASS: &'static str = "password"; + const TEST_SIMPLE_SEARCH_QUERY: &'static str = "uid=tesla,dc=example,dc=com"; + const TEST_SEARCH_BASE: &'static str = "dc=example,dc=com"; + const TEST_SEARCH_FILTER: &'static str = "(uid=euler)"; + const TEST_SEARCH_INVALID_FILTER: &'static str = "(uid=INVALID)"; + + /// Test creating a RustLDAP struct with a valid uri. + #[test] + fn test_ldap_new(){ + let _ = super::RustLDAP::new(TEST_ADDRESS).unwrap(); + } + + /// Test creating a RustLDAP struct with an invalid uri. + #[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); + } else { + assert!(false); + } + } + + #[test] + #[should_panic] + fn test_invalid_cstring_ldap_new(){ + let _ = super::RustLDAP::new("INVALID\0CSTRING").unwrap(); + } + + #[test] + fn test_simple_bind(){ + + let ldap = super::RustLDAP::new(TEST_ADDRESS).unwrap(); + let res = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).unwrap(); + println!("Bind result: {:?}", res); + + } + + #[test] + fn test_simple_search(){ + + println!("Testing simple search"); + let ldap = super::RustLDAP::new(TEST_ADDRESS).unwrap(); + let _ = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).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); + + for result in search_res { + println!("simple search result: {:?}", result); + for (key, value) in result { + println!("- key: {:?}", key); + for res_val in value { + println!("- - res_val: {:?}", res_val); + } + } + } + + } + + #[test] + fn test_search(){ + + println!("Testing search"); + let ldap = super::RustLDAP::new(TEST_ADDRESS).unwrap(); + let _ = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).unwrap(); + 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 + assert!(search_res.len() == 1); + + for result in search_res { + println!("search result: {:?}", result); + for (key, value) in result { + println!("- key: {:?}", key); + for res_val in value { + println!("- - res_val: {:?}", res_val); + } + } + } + + } + + #[test] + fn test_invalid_search(){ + + println!("Testing invalid search"); + let ldap = super::RustLDAP::new(TEST_ADDRESS).unwrap(); + let _ = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).unwrap(); + 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(); + let _ = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).unwrap(); + 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 + assert!(search_res.len() == 1); + + for result in search_res { + println!("attrs search result: {:?}", result); + for (key, value) in result { + println!("- key: {:?}", key); + for res_val in value { + println!("- - res_val: {:?}", res_val); + } + } + } + + } + + #[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(); + let _ = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).unwrap(); + 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 { + println!("attrs search result: {:?}", result); + for (key, value) in result { + println!("- key: {:?}", key); + for res_val in value { + println!("- - res_val: {:?}", res_val); + } + } + } + + } } From 10269bac129ab84cb4ee0a6037efe96532420664 Mon Sep 17 00:00:00 2001 From: Josh Leverette Date: Tue, 6 Sep 2016 10:38:28 -0500 Subject: [PATCH 06/22] Give credit where credit is due, and update version number --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9831a67..1331b05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "openldap" -version = "1.0.1" -authors = ["Josh Leverette ", "Ross Delinger "] +version = "1.1.0" +authors = ["Josh Leverette ", "Ross Delinger ", "Stephen Holsapple "] license = "MIT" readme = "README.md" repository = "https://github.com/coder543/rust-cldap" From 88fd9fc7d3463949901069584dc16c8a7facc996 Mon Sep 17 00:00:00 2001 From: Josh Leverette Date: Tue, 6 Sep 2016 10:40:31 -0500 Subject: [PATCH 07/22] Update travis link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2fb592a..e71ade2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # openldap -[![Build Status](https://travis-ci.org/sholsapp/rust-cldap.svg?branch=master)](https://travis-ci.org/sholsapp/rust-cldap) +[![Build Status](https://travis-ci.org/coder543/rust-cldap.svg?branch=master)](https://travis-ci.org/coder543/rust-cldap) Rust bindings for the native OpenLDAP library with a few convenient abstractions for connecting, binding, configuring, and querying your LDAP From 39a5df19c26cf3f442555b0e41a0586e4e2b4654 Mon Sep 17 00:00:00 2001 From: Josh Leverette Date: Tue, 6 Sep 2016 10:42:23 -0500 Subject: [PATCH 08/22] Updated README. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e71ade2..e8719cb 100644 --- a/README.md +++ b/README.md @@ -42,4 +42,4 @@ message from the native OpenLDAP library. ## developers -To author new changes to this library, do XYZ. +I'm happy to accept contributions. If you have work you want to be merged back into `master`, send me a pull request and I will be happy to look at it. I prefer changes which don't break the API, of course, but I'm willing to consider breaking changes. From dc3ecbc1c1e6b0959f591646471970d99b29f621 Mon Sep 17 00:00:00 2001 From: Josh Leverette Date: Tue, 6 Sep 2016 11:08:32 -0500 Subject: [PATCH 09/22] Fix example. `try!` cannot be in `main`. --- README.md | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index e8719cb..5d4e778 100644 --- a/README.md +++ b/README.md @@ -11,14 +11,14 @@ server. Using openldap is as easy as the following. ``` -static LDAP_URI: &'static str = "ldaps://localhost:636"; +extern crate openldap; -static LDAP_USER: &'static str = "user"; +use openldap::*; +use openldap::errors::*; -static LDAP_PASS: &'static str = "pass"; +fn some_ldap_function(ldap_uri: &str, ldap_user: &str, ldap_pass: &str) -> Result<(), LDAPError> { -fn main() { - let ldap = try!(RustLDAP::new(LDAP_URI)); + let ldap = try!(RustLDAP::new(ldap_uri)); ldap.set_option(codes::options::LDAP_OPT_PROTOCOL_VERSION, &codes::versions::LDAP_VERSION3); @@ -26,13 +26,21 @@ fn main() { ldap.set_option(codes::options::LDAP_OPT_X_TLS_REQUIRE_CERT, &codes::options::LDAP_OPT_X_TLS_DEMAND); - try!(ldap.simple_bind(LDAP_USER, LDAP_PASS)); + try!(ldap.simple_bind(ldap_user, ldap_pass)); // Returns a LDAPResponse, a.k.a. Vec>>. - let res = ldap.simple_search( - "CN=Stephen,OU=People,DC=Earth", - codes::scopes::LDAP_SCOPE_BASE, - ).unwrap(); + let _ = ldap.simple_search("CN=Stephen,OU=People,DC=Earth", + codes::scopes::LDAP_SCOPE_BASE) + .unwrap(); + + Ok(()) +} + +fn main() { + let ldap_uri = "ldaps://localhost:636"; + let ldap_user = "user"; + let ldap_pass = "pass"; + some_ldap_function(ldap_uri, ldap_user, ldap_pass).unwrap(); } ``` From a48ea803e8f1b9f785740d8bbd2dfaa28e1ddbd2 Mon Sep 17 00:00:00 2001 From: Josh Leverette Date: Tue, 6 Sep 2016 11:18:33 -0500 Subject: [PATCH 10/22] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5d4e778..5a67d28 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ server. Using openldap is as easy as the following. -``` +```rust extern crate openldap; use openldap::*; From 5ca873eb64a068531ea933beb3b3113c2ee4d6b4 Mon Sep 17 00:00:00 2001 From: Yong Wen Chua Date: Thu, 30 Mar 2017 04:13:37 +0800 Subject: [PATCH 11/22] Retrieve the DN of users when searching (#2) * Retrieve the DN of users when searchig * Add tests --- src/lib.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 88fd435..663ea98 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -45,6 +45,7 @@ extern { fn ldap_err2string(err: c_int) -> *const c_char; 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_count_values(vals: *const *const c_char) -> c_int; fn ldap_value_free(vals: *const *const c_char); @@ -360,6 +361,13 @@ impl RustLDAP { let mut map: HashMap> = HashMap::new(); let mut ber: *mut BerElement = ptr::null_mut(); unsafe { + // 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())); + 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() { @@ -385,7 +393,6 @@ impl RustLDAP { ldap_value_free(raw_vals); ldap_memfree(attr as *mut c_void); attr = ldap_next_attribute(self.ldap_ptr, entry, ber) - } // Free the BerElement and advance to the next entry. @@ -461,6 +468,9 @@ mod tests { //make sure we got something back assert!(search_res.len() == 1); + // make sure the DN is searched and returned correctly + assert_eq!(search_res[0]["dn"][0], "uid=tesla,dc=example,dc=com"); + for result in search_res { println!("simple search result: {:?}", result); for (key, value) in result { @@ -485,6 +495,9 @@ mod tests { //make sure we got something back assert!(search_res.len() == 1); + // make sure the DN is searched and returned correctly + assert_eq!(search_res[0]["dn"][0], "uid=euler,dc=example,dc=com"); + for result in search_res { println!("search result: {:?}", result); for (key, value) in result { From 2a6a095e0a7dab3ca4aa8af0778f5275c5d1b5f6 Mon Sep 17 00:00:00 2001 From: Josh Leverette Date: Wed, 29 Mar 2017 16:17:08 -0400 Subject: [PATCH 12/22] Add new author, bump version --- Cargo.toml | 4 ++-- src/lib.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1331b05..037856a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "openldap" -version = "1.1.0" -authors = ["Josh Leverette ", "Ross Delinger ", "Stephen Holsapple "] +version = "1.2.0" +authors = ["Josh Leverette ", "Ross Delinger ", "Stephen Holsapple ", "Yong Wen Chua "] license = "MIT" readme = "README.md" repository = "https://github.com/coder543/rust-cldap" diff --git a/src/lib.rs b/src/lib.rs index 663ea98..99f68bb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -383,7 +383,7 @@ impl RustLDAP { // Map these into a vector of Strings. let values: Vec = val_slice.iter().map(|ptr| { // TODO(sholsapp): If this contains binary data this will fail. - CStr::from_ptr(*ptr).to_owned().into_string().unwrap_or("".to_string()) + CStr::from_ptr(*ptr).to_owned().into_string().unwrap_or("".to_string()) }).collect(); // Insert newly constructed Rust key-value strings. From 3881dcc98ac709d664917e0ca4975e79299a5b28 Mon Sep 17 00:00:00 2001 From: Josh Leverette Date: Wed, 29 Mar 2017 16:19:57 -0400 Subject: [PATCH 13/22] Fix build warning --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 99f68bb..44d010a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -137,7 +137,7 @@ impl LDAPOptionValue for bool { fn as_cvoid_ptr(&self) -> *const c_void { match *self { true => { - let mem = boxed::Box::new(&ber_pvt_opt_on); + let mem = unsafe { boxed::Box::new(&ber_pvt_opt_on) }; boxed::Box::into_raw(mem) as *const c_void }, false => { From c31acdbebf8d0f778ed65d4e6379d6b29c9c31cb Mon Sep 17 00:00:00 2001 From: Josh Leverette Date: Wed, 29 Mar 2017 16:25:21 -0400 Subject: [PATCH 14/22] Run cargo fmt on the source code --- src/errors.rs | 4 +- src/lib.rs | 244 +++++++++++++++++++++++++++++++------------------- 2 files changed, 154 insertions(+), 94 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index 88ee783..2c9ac8a 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -28,12 +28,11 @@ impl fmt::Display for LDAPError { } impl error::Error for LDAPError { - /// Get the description of this error. /// fn description(&self) -> &str { match *self { - LDAPError::NativeError(ref err) => err + LDAPError::NativeError(ref err) => err, } } @@ -51,4 +50,3 @@ impl convert::From for LDAPError { LDAPError::NativeError(err) } } - diff --git a/src/lib.rs b/src/lib.rs index 44d010a..d64c20b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,13 +31,13 @@ unsafe impl Send for LDAP {} #[link(name = "lber")] #[allow(improper_ctypes)] -extern { +extern "C" { fn ber_free(ber: *const BerElement, freebuf: c_int); } #[link(name = "ldap_r")] #[allow(improper_ctypes)] -extern { +extern "C" { static ber_pvt_opt_on: c_char; fn ldap_initialize(ldap: *mut *mut LDAP, uri: *const c_char) -> c_int; fn ldap_memfree(p: *mut c_void); @@ -46,19 +46,38 @@ extern { 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_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; } /// A typedef for an LDAPResponse type. @@ -66,7 +85,7 @@ extern { /// LDAP responses are organized as vectors of mached entities. Typically, each entity is /// represented as a map of attributes to list of values. /// -pub type LDAPResponse = Vec>>; +pub type LDAPResponse = Vec>>; /// A high level abstraction over the raw OpenLDAP functions. @@ -87,7 +106,7 @@ unsafe impl Sync for RustLDAP {} unsafe impl Send for RustLDAP {} impl Drop for RustLDAP { - fn drop(&mut self){ + fn drop(&mut self) { // Unbind the LDAP connection, making the C library free the LDAP*. let rc = unsafe { ldap_unbind_ext_s(self.ldap_ptr, ptr::null_mut(), ptr::null_mut()) }; @@ -139,7 +158,7 @@ impl LDAPOptionValue for bool { true => { let mem = unsafe { boxed::Box::new(&ber_pvt_opt_on) }; boxed::Box::into_raw(mem) as *const c_void - }, + } false => { let mem = boxed::Box::new(0); boxed::Box::into_raw(mem) as *const c_void @@ -171,14 +190,15 @@ 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())); } } - let new_ldap = RustLDAP { - ldap_ptr: cldap, - }; + let new_ldap = RustLDAP { ldap_ptr: cldap }; return Ok(new_ldap); } @@ -196,14 +216,10 @@ impl RustLDAP { let ptr: *const c_void = value.as_cvoid_ptr(); unsafe { let res: i32; - res = ldap_set_option( - self.ldap_ptr, - option, - ptr, - ); + res = ldap_set_option(self.ldap_ptr, option, ptr); // Allows for memory to be dropped when this binding goes away. let _ = boxed::Box::from_raw(ptr as *mut c_void); - return res == 0 + return res == 0; } } @@ -229,7 +245,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())); } return Ok(res); } @@ -245,17 +264,15 @@ impl RustLDAP { /// * scope - The search scope. See `cldap::codes::scopes`. /// pub fn simple_search(&self, base: &str, scope: i32) -> Result { - return self.ldap_search( - base, - scope, - None, - None, - false, - None, - None, - ptr::null_mut(), - -1, - ); + return self.ldap_search(base, + scope, + None, + None, + false, + None, + None, + ptr::null_mut(), + -1); } /// Advanced synchronous search. @@ -285,10 +302,10 @@ impl RustLDAP { clientctrls: Option<*mut *mut LDAPControl>, timeout: *mut timeval, sizelimit: i32) - -> Result { + -> Result { // Make room for the LDAPMessage, being sure to delete this before we return. - let mut ldap_msg = ptr::null_mut();; + let mut ldap_msg = ptr::null_mut(); // Convert the passed in filter sting to either a C-string or null if one is not passed. let filter_cstr: CString; @@ -296,8 +313,8 @@ impl RustLDAP { Some(fs) => { filter_cstr = CString::new(fs).unwrap(); filter_cstr.as_ptr() - }, - None => ptr::null() + } + None => ptr::null(), }; // Convert the vec of attributes into the null-terminated array that the library expects. @@ -319,52 +336,56 @@ impl RustLDAP { let r_serverctrls = match serverctrls { Some(sc) => sc, - None => ptr::null_mut() + None => ptr::null_mut(), }; let r_clientctrls = match clientctrls { Some(cc) => cc, - None => ptr::null_mut() + None => ptr::null_mut(), }; 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())); } } // We now have to parse the results, copying the C-strings into Rust ones making sure to // free the C-strings afterwards - let mut resvec: Vec>> = vec![]; + let mut resvec: Vec>> = vec![]; 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(); + let mut map: HashMap> = HashMap::new(); let mut ber: *mut BerElement = ptr::null_mut(); unsafe { // 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); @@ -376,15 +397,21 @@ impl RustLDAP { let key = CStr::from_ptr(attr).to_owned().into_string().unwrap(); // Get the attribute values from LDAP. - let raw_vals: *const *const c_char = ldap_get_values(self.ldap_ptr, entry, attr); + let raw_vals: *const *const c_char = + ldap_get_values(self.ldap_ptr, entry, attr); let raw_vals_len = ldap_count_values(raw_vals) as usize; 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().map(|ptr| { - // TODO(sholsapp): If this contains binary data this will fail. - CStr::from_ptr(*ptr).to_owned().into_string().unwrap_or("".to_string()) - }).collect(); + let values: Vec = val_slice.iter() + .map(|ptr| { + // TODO(sholsapp): If this contains binary data this will fail. + CStr::from_ptr(*ptr) + .to_owned() + .into_string() + .unwrap_or("".to_string()) + }) + .collect(); // Insert newly constructed Rust key-value strings. map.insert(key, values); @@ -418,25 +445,27 @@ mod tests { 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"; - const TEST_BIND_PASS: &'static str = "password"; - const TEST_SIMPLE_SEARCH_QUERY: &'static str = "uid=tesla,dc=example,dc=com"; - const TEST_SEARCH_BASE: &'static str = "dc=example,dc=com"; - const TEST_SEARCH_FILTER: &'static str = "(uid=euler)"; - const TEST_SEARCH_INVALID_FILTER: &'static str = "(uid=INVALID)"; + const TEST_ADDRESS: &'static str = "ldap://ldap.forumsys.com"; + const TEST_BIND_DN: &'static str = "cn=read-only-admin,dc=example,dc=com"; + const TEST_BIND_PASS: &'static str = "password"; + const TEST_SIMPLE_SEARCH_QUERY: &'static str = "uid=tesla,dc=example,dc=com"; + const TEST_SEARCH_BASE: &'static str = "dc=example,dc=com"; + const TEST_SEARCH_FILTER: &'static str = "(uid=euler)"; + const TEST_SEARCH_INVALID_FILTER: &'static str = "(uid=INVALID)"; /// Test creating a RustLDAP struct with a valid uri. #[test] - fn test_ldap_new(){ + fn test_ldap_new() { let _ = super::RustLDAP::new(TEST_ADDRESS).unwrap(); } /// Test creating a RustLDAP struct with an invalid uri. #[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); + 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); } else { assert!(false); } @@ -444,12 +473,12 @@ mod tests { #[test] #[should_panic] - fn test_invalid_cstring_ldap_new(){ + fn test_invalid_cstring_ldap_new() { let _ = super::RustLDAP::new("INVALID\0CSTRING").unwrap(); } #[test] - fn test_simple_bind(){ + fn test_simple_bind() { let ldap = super::RustLDAP::new(TEST_ADDRESS).unwrap(); let res = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).unwrap(); @@ -458,12 +487,13 @@ mod tests { } #[test] - fn test_simple_search(){ + fn test_simple_search() { println!("Testing simple search"); let ldap = super::RustLDAP::new(TEST_ADDRESS).unwrap(); let _ = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).unwrap(); - 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); @@ -484,13 +514,21 @@ mod tests { } #[test] - fn test_search(){ + fn test_search() { println!("Testing search"); let ldap = super::RustLDAP::new(TEST_ADDRESS).unwrap(); let _ = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).unwrap(); - 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(); + 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 assert!(search_res.len() == 1); @@ -511,13 +549,21 @@ mod tests { } #[test] - fn test_invalid_search(){ + fn test_invalid_search() { println!("Testing invalid search"); let ldap = super::RustLDAP::new(TEST_ADDRESS).unwrap(); let _ = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).unwrap(); - 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(); + 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); @@ -525,14 +571,22 @@ mod tests { } #[test] - fn test_search_attrs(){ + 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(); let _ = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).unwrap(); - 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(); + 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 assert!(search_res.len() == 1); @@ -550,14 +604,22 @@ mod tests { } #[test] - fn test_search_invalid_attrs(){ + 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(); let _ = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).unwrap(); - 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(); + 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 { println!("attrs search result: {:?}", result); From 2baf81ecbe33ee8d629d7f0c0ba9830753ab47d8 Mon Sep 17 00:00:00 2001 From: Josh Leverette Date: Wed, 29 Mar 2017 16:51:52 -0400 Subject: [PATCH 15/22] Make code a bit more idiomatic and fix some clippy warnings --- src/errors.rs | 4 +-- src/lib.rs | 74 ++++++++++++++++++++++++--------------------------- 2 files changed, 37 insertions(+), 41 deletions(-) diff --git a/src/errors.rs b/src/errors.rs index 2c9ac8a..65cdb99 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -11,8 +11,8 @@ use std::convert; /// only one type of error raised: `LDAPError::NativeError`. A `LDAPError::NativeError` includes a /// string field describing the error in more detail. /// -/// A `LDAPError` implements necessary traits (i.e., std::fmt::Display, std::error::Error, and -/// std::convert::From) to do proper error handling using the `try!` macro. +/// A `LDAPError` implements necessary traits (i.e., `std::fmt::Display`, `std::error::Error`, and +/// `std::convert::From`) to do proper error handling using the `try!` macro. /// #[derive(Debug, PartialEq)] pub enum LDAPError { diff --git a/src/lib.rs b/src/lib.rs index d64c20b..cd2cbd3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -//! Objects for connecting and querying LDAP servers using OpenLDAP. +//! Objects for connecting and querying LDAP servers using `OpenLDAP`. //! //! Current support includes connection, initializing, binding, configuring, and search against an //! LDAP directory. @@ -80,7 +80,7 @@ extern "C" { -> c_int; } -/// A typedef for an LDAPResponse type. +/// A typedef for an `LDAPResponse` type. /// /// LDAP responses are organized as vectors of mached entities. Typically, each entity is /// represented as a map of attributes to list of values. @@ -88,16 +88,16 @@ extern "C" { pub type LDAPResponse = Vec>>; -/// A high level abstraction over the raw OpenLDAP functions. +/// A high level abstraction over the raw `OpenLDAP` functions. /// -/// A `RustLDAP` object hides raw OpenLDAP complexities and exposes a simple object that is -/// created, configured, and queried. Methods that call underlying OpenLDAP calls that can fail +/// A `RustLDAP` object hides raw `OpenLDAP` complexities and exposes a simple object that is +/// created, configured, and queried. Methods that call underlying `OpenLDAP` calls that can fail /// will raise an `errors::LDAPError` with additional details. /// /// Using a `RustLDAP` object is easy! /// pub struct RustLDAP { - /// A pointer to the underlying OpenLDAP object. + /// A pointer to the underlying `OpenLDAP` object. ldap_ptr: *mut LDAP, } @@ -123,13 +123,13 @@ impl Drop for RustLDAP { /// A trait for types that can be passed as LDAP option values. /// -/// Underlying OpenLDAP implementation calls for option values to be passed in as *const c_void, -/// while allowing values to be i32 or string. Using traits, we implement function overloading to -/// handle i32 and string option value types. +/// Underlying `OpenLDAP` implementation calls for option values to be passed in as `*const c_void`, +/// while allowing values to be `i32` or `String`. Using traits, we implement function overloading to +/// handle `i32` and `String` option value types. /// /// This trait allocates memory that a caller must free using `std::boxed::Box::from_raw`. This /// helps guarantee that there is not a use after free bug (in Rust) while providing the appearance -/// of opaque memory to OpenLDAP (in C). In pure C, we would've accomplished this by casting a +/// of opaque memory to `OpenLDAP` (in C). In pure C, we would've accomplished this by casting a /// local variable to a `const void *`. In Rust, we must do this on the heap to ensure Rust's /// ownership system does not free the memory used to store the option value between now and when /// the option is actually set. @@ -154,26 +154,23 @@ impl LDAPOptionValue for i32 { impl LDAPOptionValue for bool { fn as_cvoid_ptr(&self) -> *const c_void { - match *self { - true => { - let mem = unsafe { boxed::Box::new(&ber_pvt_opt_on) }; - boxed::Box::into_raw(mem) as *const c_void - } - false => { - let mem = boxed::Box::new(0); - boxed::Box::into_raw(mem) as *const c_void - } + if *self { + let mem = unsafe { boxed::Box::new(&ber_pvt_opt_on) }; + boxed::Box::into_raw(mem) as *const c_void + } else { + let mem = boxed::Box::new(0); + boxed::Box::into_raw(mem) as *const c_void } } } impl RustLDAP { - /// Creat a new RustLDAP. + /// Create a new `RustLDAP`. /// - /// Creates a new RustLDAP and initializes underlying OpenLDAP library. Upon creation, a + /// 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 - /// information for more details on using a RustLDAP object. + /// information for more details on using a `RustLDAP` object. /// /// # Parameters /// @@ -198,8 +195,7 @@ impl RustLDAP { } - let new_ldap = RustLDAP { ldap_ptr: cldap }; - return Ok(new_ldap); + Ok(RustLDAP { ldap_ptr: cldap }) } /// Sets an option on the LDAP connection. @@ -219,16 +215,15 @@ impl RustLDAP { res = ldap_set_option(self.ldap_ptr, option, ptr); // Allows for memory to be dropped when this binding goes away. let _ = boxed::Box::from_raw(ptr as *mut c_void); - return res == 0; + res == 0 } - } /// Bind to the LDAP server. /// /// If you wish to configure options on the LDAP server, be sure to set required options using ///`set_option` _before_ binding to the LDAP server. In some advanced cases, it may be required - /// to set multiple options for an option to be made available. Refer to the OpenLDAP + /// to set multiple options for an option to be made available. Refer to the `OpenLDAP` /// documentation for information on available options and how to use them. /// /// # Parameters @@ -250,7 +245,7 @@ impl RustLDAP { .into_string() .unwrap())); } - return Ok(res); + Ok(res) } } @@ -264,20 +259,20 @@ impl RustLDAP { /// * scope - The search scope. See `cldap::codes::scopes`. /// pub fn simple_search(&self, base: &str, scope: i32) -> Result { - return 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) } /// Advanced synchronous search. /// - /// Exposes a raw API around the underlying `ldap_search_ext_s` function from OpenLDAP. + /// Exposes a raw API around the underlying `ldap_search_ext_s` function from `OpenLDAP`. /// Wherever possible, use provided wrappers. /// /// # Parameters @@ -435,7 +430,8 @@ impl RustLDAP { // Make sure we free the message and return the parsed results. unsafe { ldap_msgfree(ldap_msg) }; - return Ok(resvec); + + Ok(resvec) } } From 9b030eb5af1d5065e63c8e525ca04b6f65e3c445 Mon Sep 17 00:00:00 2001 From: Josh Leverette Date: Wed, 29 Mar 2017 16:54:43 -0400 Subject: [PATCH 16/22] Add documentation link and bump version number again.. --- Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 037856a..90311b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,12 @@ [package] name = "openldap" -version = "1.2.0" +version = "1.2.1" authors = ["Josh Leverette ", "Ross Delinger ", "Stephen Holsapple ", "Yong Wen Chua "] license = "MIT" readme = "README.md" repository = "https://github.com/coder543/rust-cldap" homepage = "https://github.com/coder543/rust-cldap" +documentation = "https://docs.rs/openldap/" description = "Straightforward Rust bindings to the C openldap library. This is a fork of cldap that has been methodically fixed, extended, and made to be more compliant with openldap. It should be relatively robust and production ready at this point." [dependencies] From a3001cac849a032350721c3cc446f7fbbb3f41d9 Mon Sep 17 00:00:00 2001 From: Josh Leverette Date: Wed, 29 Mar 2017 16:59:52 -0400 Subject: [PATCH 17/22] Tests will fail because test server is rate limited. Remove misleading travis badge. --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 5a67d28..38c78e4 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # openldap -[![Build Status](https://travis-ci.org/coder543/rust-cldap.svg?branch=master)](https://travis-ci.org/coder543/rust-cldap) - Rust bindings for the native OpenLDAP library with a few convenient abstractions for connecting, binding, configuring, and querying your LDAP server. From 407c3f662aed0c41f0ef985c8716e6e8ccc20d59 Mon Sep 17 00:00:00 2001 From: Yong Wen Chua Date: Sat, 8 Apr 2017 00:33:21 +0800 Subject: [PATCH 18/22] Set LDAP Protocl Version and assert bindings are successful (#3) --- src/lib.rs | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index cd2cbd3..ed47195 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -475,9 +475,10 @@ mod tests { #[test] fn test_simple_bind() { - 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); println!("Bind result: {:?}", res); } @@ -487,7 +488,9 @@ mod tests { println!("Testing simple search"); let ldap = super::RustLDAP::new(TEST_ADDRESS).unwrap(); - let _ = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).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(); @@ -514,7 +517,9 @@ mod tests { println!("Testing search"); let ldap = super::RustLDAP::new(TEST_ADDRESS).unwrap(); - let _ = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).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), @@ -549,7 +554,9 @@ mod tests { println!("Testing invalid search"); let ldap = super::RustLDAP::new(TEST_ADDRESS).unwrap(); - let _ = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).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), @@ -572,7 +579,9 @@ mod tests { println!("Testing search with attrs"); let test_search_attrs_vec = vec!["cn", "sn", "mail"]; let ldap = super::RustLDAP::new(TEST_ADDRESS).unwrap(); - let _ = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).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), @@ -605,7 +614,9 @@ mod tests { println!("Testing search with invalid attrs"); let test_search_attrs_vec = vec!["cn", "sn", "mail", "INVALID"]; let ldap = super::RustLDAP::new(TEST_ADDRESS).unwrap(); - let _ = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).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), From 1f913243b6d03abc90edfb80eb83ff3d1f8194fa Mon Sep 17 00:00:00 2001 From: Josh Leverette Date: Fri, 7 Apr 2017 12:34:31 -0400 Subject: [PATCH 19/22] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 38c78e4..04644a2 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,6 @@ When performing an operation that can fail, use the `try!` macro. On failure, an `openldap::errors::LDAPError` will be returned that includes a detailed message from the native OpenLDAP library. -## developers +## contributing I'm happy to accept contributions. If you have work you want to be merged back into `master`, send me a pull request and I will be happy to look at it. I prefer changes which don't break the API, of course, but I'm willing to consider breaking changes. From 263a5c29c5bc9a88a6656a46c81655f8071977b9 Mon Sep 17 00:00:00 2001 From: Mathias Myrland Date: Fri, 10 Jan 2020 13:15:35 +0100 Subject: [PATCH 20/22] Added start_tls_s support --- README.md | 21 +++++++++++++++ src/lib.rs | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) diff --git a/README.md b/README.md index 04644a2..fa67193 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,27 @@ fn main() { } ``` +### Security + +You should use *start_tls* before calling bind to avoid sending credentials in plain text over an untrusted +network. See https://linux.die.net/man/3/ldap_start_tls_s for more information + +```rust +fn some_ldap_function(ldap_uri: &str, ldap_user: &str, ldap_pass: &str) -> Result<(), LDAPError> { + let ldap = try!(RustLDAP::new(ldap_uri)); + + ldap.set_option(codes::options::LDAP_OPT_PROTOCOL_VERSION, + &codes::versions::LDAP_VERSION3); + + ldap.set_option(codes::options::LDAP_OPT_X_TLS_REQUIRE_CERT, + &codes::options::LDAP_OPT_X_TLS_DEMAND); + ldap.set_option(openldap::codes::options::LDAP_OPT_X_TLS_NEWCTX, &0); + ldap.start_tls(None, None); + + try!(ldap.simple_bind(ldap_user, ldap_pass)); +} + +``` When performing an operation that can fail, use the `try!` macro. On failure, an `openldap::errors::LDAPError` will be returned that includes a detailed message from the native OpenLDAP library. diff --git a/src/lib.rs b/src/lib.rs index ed47195..bb74604 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,7 @@ 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; @@ -78,6 +79,9 @@ extern "C" { 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. @@ -270,6 +274,55 @@ impl RustLDAP { -1) } + /// Installs TLS handlers on the session + /// + /// # Examples + /// + /// ``` + /// use openldap::RustLDAP; + /// let ldap = RustLDAP::new(&"ldaps://myserver:636"); + /// + /// ldap.set_option( + /// openldap::codes::options::LDAP_OPT_PROTOCOL_VERSION, + /// &openldap::codes::versions::LDAP_VERSION3, + /// ); + /// + /// ldap.set_option( + /// openldap::codes::options::LDAP_OPT_X_TLS_REQUIRE_CERT, + /// &openldap::codes::options::LDAP_OPT_X_TLS_ALLOW, + /// ); + /// + /// ldap.set_option(openldap::codes::options::LDAP_OPT_X_TLS_NEWCTX, &0); + /// + /// 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 { + let r_serverctrls = match serverctrls { + Some(sc) => sc, + None => ptr::null_mut(), + }; + + let r_clientctrls = match clientctrls { + Some(cc) => cc, + None => ptr::null_mut(), + }; + + unsafe { + let res = ldap_start_tls_s(self.ldap_ptr, r_serverctrls, r_clientctrls); + + 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())); + } + + Ok(res) + } + } + /// Advanced synchronous search. /// /// Exposes a raw API around the underlying `ldap_search_ext_s` function from `OpenLDAP`. @@ -483,6 +536,32 @@ mod tests { } + #[test] + fn test_simple_bind_with_start_tls() { + 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.set_option( + codes::options::LDAP_OPT_PROTOCOL_VERSION, + &codes::versions::LDAP_VERSION3, + ); + + ldap.set_option( + codes::options::LDAP_OPT_X_TLS_REQUIRE_CERT, + &codes::options::LDAP_OPT_X_TLS_ALLOW, + ); + + ldap.set_option(codes::options::LDAP_OPT_X_TLS_NEWCTX, &0); + + 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() { From a1ebc6d001bb62f4a351e2b4e80f0723f5a2d87d Mon Sep 17 00:00:00 2001 From: Mathias Myrland Date: Fri, 10 Jan 2020 22:35:45 +0100 Subject: [PATCH 21/22] (add-start-tls) updated example code in the readme, removed references to try! --- README.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index fa67193..60b1d6e 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,7 @@ use openldap::*; use openldap::errors::*; fn some_ldap_function(ldap_uri: &str, ldap_user: &str, ldap_pass: &str) -> Result<(), LDAPError> { - - let ldap = try!(RustLDAP::new(ldap_uri)); + let ldap = RustLDAP::new(ldap_uri).unwrap(); ldap.set_option(codes::options::LDAP_OPT_PROTOCOL_VERSION, &codes::versions::LDAP_VERSION3); @@ -24,7 +23,7 @@ fn some_ldap_function(ldap_uri: &str, ldap_user: &str, ldap_pass: &str) -> Resul ldap.set_option(codes::options::LDAP_OPT_X_TLS_REQUIRE_CERT, &codes::options::LDAP_OPT_X_TLS_DEMAND); - try!(ldap.simple_bind(ldap_user, ldap_pass)); + ldap.simple_bind(ldap_user, ldap_pass).unwrap(); // Returns a LDAPResponse, a.k.a. Vec>>. let _ = ldap.simple_search("CN=Stephen,OU=People,DC=Earth", @@ -49,7 +48,7 @@ network. See https://linux.die.net/man/3/ldap_start_tls_s for more information ```rust fn some_ldap_function(ldap_uri: &str, ldap_user: &str, ldap_pass: &str) -> Result<(), LDAPError> { - let ldap = try!(RustLDAP::new(ldap_uri)); + let ldap = RustLDAP::new(ldap_uri).unwrap(); ldap.set_option(codes::options::LDAP_OPT_PROTOCOL_VERSION, &codes::versions::LDAP_VERSION3); @@ -57,14 +56,16 @@ fn some_ldap_function(ldap_uri: &str, ldap_user: &str, ldap_pass: &str) -> Resul ldap.set_option(codes::options::LDAP_OPT_X_TLS_REQUIRE_CERT, &codes::options::LDAP_OPT_X_TLS_DEMAND); ldap.set_option(openldap::codes::options::LDAP_OPT_X_TLS_NEWCTX, &0); + ldap.start_tls(None, None); - try!(ldap.simple_bind(ldap_user, ldap_pass)); + ldap.simple_bind(ldap_user, ldap_pass).unwrap(); + + Ok(()) } ``` -When performing an operation that can fail, use the `try!` macro. On failure, -an `openldap::errors::LDAPError` will be returned that includes a detailed +On failure, an `openldap::errors::LDAPError` will be returned that includes a detailed message from the native OpenLDAP library. ## contributing From f9b890ae9fe7ca3c288645a078db7ed21be46ae4 Mon Sep 17 00:00:00 2001 From: Josh Leverette Date: Tue, 14 Jan 2020 20:04:40 -0500 Subject: [PATCH 22/22] bump version, ensure tests don't fail --- Cargo.toml | 4 ++-- src/lib.rs | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 90311b6..fbdef95 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,13 @@ [package] name = "openldap" -version = "1.2.1" +version = "1.2.2" authors = ["Josh Leverette ", "Ross Delinger ", "Stephen Holsapple ", "Yong Wen Chua "] license = "MIT" readme = "README.md" repository = "https://github.com/coder543/rust-cldap" homepage = "https://github.com/coder543/rust-cldap" documentation = "https://docs.rs/openldap/" -description = "Straightforward Rust bindings to the C openldap library. This is a fork of cldap that has been methodically fixed, extended, and made to be more compliant with openldap. It should be relatively robust and production ready at this point." +description = "Straightforward Rust bindings to the C openldap library. This is a fork of cldap that has been methodically fixed, extended, and made to be more compliant with openldap. It should be relatively robust and production ready at this point. Not heavily maintained, but feel free to send PRs if you see something missing." [dependencies] libc = "0.2.10" diff --git a/src/lib.rs b/src/lib.rs index bb74604..d0b1406 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -278,9 +278,9 @@ impl RustLDAP { /// /// # Examples /// - /// ``` + /// ```should_panic /// use openldap::RustLDAP; - /// let ldap = RustLDAP::new(&"ldaps://myserver:636"); + /// let ldap = RustLDAP::new(&"ldaps://myserver:636").unwrap(); /// /// ldap.set_option( /// openldap::codes::options::LDAP_OPT_PROTOCOL_VERSION, @@ -295,7 +295,7 @@ impl RustLDAP { /// ldap.set_option(openldap::codes::options::LDAP_OPT_X_TLS_NEWCTX, &0); /// /// ldap.start_tls(None, None); - /// ldap.simple_bind("some-dn", "some-password").unwrap() + /// ldap.simple_bind("some-dn", "some-password").unwrap(); /// ``` pub fn start_tls(&self, serverctrls: Option<*mut *mut LDAPControl>, clientctrls: Option<*mut *mut LDAPControl>) -> Result { let r_serverctrls = match serverctrls { @@ -537,6 +537,7 @@ mod tests { } #[test] + #[should_panic] // the TEST_ADDRESS being used doesn't support LDAPS, only LDAP fn test_simple_bind_with_start_tls() { let ldap = super::RustLDAP::new(TEST_ADDRESS).unwrap();