├── .gitignore ├── .travis.yml ├── Cargo.toml ├── src ├── errors.rs ├── codes.rs └── lib.rs └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | *.swp 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | sudo: false 3 | rust: 4 | - stable 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "openldap" 3 | version = "1.2.2" 4 | authors = ["Josh Leverette ", "Ross Delinger ", "Stephen Holsapple ", "Yong Wen Chua "] 5 | license = "MIT" 6 | readme = "README.md" 7 | repository = "https://github.com/coder543/rust-cldap" 8 | homepage = "https://github.com/coder543/rust-cldap" 9 | documentation = "https://docs.rs/openldap/" 10 | 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." 11 | 12 | [dependencies] 13 | libc = "0.2.10" 14 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | //! Errors and trait implementations. 2 | //! 3 | use std::fmt; 4 | use std::error; 5 | use std::convert; 6 | 7 | 8 | /// A LDAP error. 9 | /// 10 | /// LDAP errors occur when an underlying function returns with an error code. Currently, there is 11 | /// only one type of error raised: `LDAPError::NativeError`. A `LDAPError::NativeError` includes a 12 | /// string field describing the error in more detail. 13 | /// 14 | /// A `LDAPError` implements necessary traits (i.e., `std::fmt::Display`, `std::error::Error`, and 15 | /// `std::convert::From`) to do proper error handling using the `try!` macro. 16 | /// 17 | #[derive(Debug, PartialEq)] 18 | pub enum LDAPError { 19 | NativeError(String), 20 | } 21 | 22 | impl fmt::Display for LDAPError { 23 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 24 | match *self { 25 | LDAPError::NativeError(ref err) => write!(f, "LDAP error: {}", err), 26 | } 27 | } 28 | } 29 | 30 | impl error::Error for LDAPError { 31 | /// Get the description of this error. 32 | /// 33 | fn description(&self) -> &str { 34 | match *self { 35 | LDAPError::NativeError(ref err) => err, 36 | } 37 | } 38 | 39 | /// Get the cause of this error. 40 | /// 41 | /// Note, currently this method always return `None` as we do not know the root cause of the 42 | /// error. 43 | fn cause(&self) -> Option<&error::Error> { 44 | None 45 | } 46 | } 47 | 48 | impl convert::From for LDAPError { 49 | fn from(err: String) -> LDAPError { 50 | LDAPError::NativeError(err) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # openldap 2 | 3 | Rust bindings for the native OpenLDAP library with a few convenient 4 | abstractions for connecting, binding, configuring, and querying your LDAP 5 | server. 6 | 7 | ## usage 8 | 9 | Using openldap is as easy as the following. 10 | 11 | ```rust 12 | extern crate openldap; 13 | 14 | use openldap::*; 15 | use openldap::errors::*; 16 | 17 | fn some_ldap_function(ldap_uri: &str, ldap_user: &str, ldap_pass: &str) -> Result<(), LDAPError> { 18 | let ldap = RustLDAP::new(ldap_uri).unwrap(); 19 | 20 | ldap.set_option(codes::options::LDAP_OPT_PROTOCOL_VERSION, 21 | &codes::versions::LDAP_VERSION3); 22 | 23 | ldap.set_option(codes::options::LDAP_OPT_X_TLS_REQUIRE_CERT, 24 | &codes::options::LDAP_OPT_X_TLS_DEMAND); 25 | 26 | ldap.simple_bind(ldap_user, ldap_pass).unwrap(); 27 | 28 | // Returns a LDAPResponse, a.k.a. Vec>>. 29 | let _ = ldap.simple_search("CN=Stephen,OU=People,DC=Earth", 30 | codes::scopes::LDAP_SCOPE_BASE) 31 | .unwrap(); 32 | 33 | Ok(()) 34 | } 35 | 36 | fn main() { 37 | let ldap_uri = "ldaps://localhost:636"; 38 | let ldap_user = "user"; 39 | let ldap_pass = "pass"; 40 | some_ldap_function(ldap_uri, ldap_user, ldap_pass).unwrap(); 41 | } 42 | ``` 43 | 44 | ### Security 45 | 46 | You should use *start_tls* before calling bind to avoid sending credentials in plain text over an untrusted 47 | network. See https://linux.die.net/man/3/ldap_start_tls_s for more information 48 | 49 | ```rust 50 | fn some_ldap_function(ldap_uri: &str, ldap_user: &str, ldap_pass: &str) -> Result<(), LDAPError> { 51 | let ldap = RustLDAP::new(ldap_uri).unwrap(); 52 | 53 | ldap.set_option(codes::options::LDAP_OPT_PROTOCOL_VERSION, 54 | &codes::versions::LDAP_VERSION3); 55 | 56 | ldap.set_option(codes::options::LDAP_OPT_X_TLS_REQUIRE_CERT, 57 | &codes::options::LDAP_OPT_X_TLS_DEMAND); 58 | ldap.set_option(openldap::codes::options::LDAP_OPT_X_TLS_NEWCTX, &0); 59 | 60 | ldap.start_tls(None, None); 61 | 62 | ldap.simple_bind(ldap_user, ldap_pass).unwrap(); 63 | 64 | Ok(()) 65 | } 66 | 67 | ``` 68 | On failure, an `openldap::errors::LDAPError` will be returned that includes a detailed 69 | message from the native OpenLDAP library. 70 | 71 | ## contributing 72 | 73 | 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. 74 | -------------------------------------------------------------------------------- /src/codes.rs: -------------------------------------------------------------------------------- 1 | //! LDAP codes from transcribed from ldap.h. 2 | //! 3 | 4 | /// Re-export protocol result codes from ldap.h 5 | pub mod results { 6 | pub static LDAP_SUCCESS: i32 = 0x00; 7 | pub static LDAP_OPERATIONS_ERROR: i32 = 0x01; 8 | pub static LDAP_PROTOCOL_ERROR: i32 = 0x02; 9 | pub static LDAP_TIMELIMIT_EXCEEDED: i32 = 0x03; 10 | pub static LDAP_SIZELIMIT_EXCEEDED: i32 = 0x04; 11 | pub static LDAP_COMPARE_FALSE: i32 = 0x05; 12 | pub static LDAP_COMPARE_TRUE: i32 = 0x06; 13 | pub static LDAP_AUTH_METHOD_NOT_SUPPORTED: i32 = 0x07; 14 | pub static LDAP_STRONG_AUTH_NOT_SUPPORTED: i32 = 0x07; 15 | pub static LDAP_STRONG_AUTH_REQUIRED: i32 = 0x08; 16 | pub static LDAP_STRONGER_AUTH_REQUIRED: i32 = 0x08; 17 | pub static LDAP_PARTIAL_RESULTS: i32 = 0x09; 18 | 19 | pub static LDAP_REFERRAL: i32 = 0x0a; 20 | pub static LDAP_ADMINLIMIT_EXCEEDED: i32 = 0x0b; 21 | pub static LDAP_UNAVAILABLE_CRITICAL_EXTENSION: i32 = 0x0c; 22 | pub static LDAP_CONFIDENTIALITY_REQUIRED: i32 = 0x0d; 23 | pub static LDAP_SASL_BIND_IN_PROGRESS: i32 = 0x0e; 24 | 25 | pub static LDAP_NO_SUCH_ATTRIBUTE: i32 = 0x10; 26 | pub static LDAP_UNDEFINED_TYPE: i32 = 0x11; 27 | pub static LDAP_INAPPROPRIATE_MATCHING: i32 = 0x12; 28 | pub static LDAP_CONSTRAINT_VIOLATION: i32 = 0x13; 29 | pub static LDAP_TYPE_OR_VALUE_EXISTS: i32 = 0x14; 30 | pub static LDAP_INVALID_SYNTAX: i32 = 0x15; 31 | 32 | pub static LDAP_NO_SUCH_OBJECT: i32 = 0x20; 33 | pub static LDAP_ALIAS_PROBLEM: i32 = 0x21; 34 | pub static LDAP_INVALID_DN_SYNTAX: i32 = 0x22; 35 | pub static LDAP_IS_LEAF: i32 = 0x23; 36 | pub static LDAP_ALIAS_DEREF_PROBLEM: i32 = 0x24; 37 | 38 | pub static LDAP_X_PROXY_AUTHZ_FAILURE: i32 = 0x2f; 39 | pub static LDAP_INAPPROPRIATE_AUTH: i32 = 0x30; 40 | pub static LDAP_INVALID_CREDENTIALS: i32 = 0x31; 41 | pub static LDAP_INSUFFICIENT_ACCESS: i32 = 0x32; 42 | 43 | pub static LDAP_BUSY: i32 = 0x33; 44 | pub static LDAP_UNAVAILABLE: i32 = 0x34; 45 | pub static LDAP_UNWILLING_TO_PERFORM: i32 = 0x35; 46 | pub static LDAP_LOOP_DETECT: i32 = 0x36; 47 | 48 | pub static LDAP_NAMING_VIOLATION: i32 = 0x40; 49 | pub static LDAP_OBJECT_CLASS_VIOLATION: i32 = 0x41; 50 | pub static LDAP_NOT_ALLOWED_ON_NONLEAF: i32 = 0x42; 51 | pub static LDAP_NOT_ALLOWED_ON_RDN: i32 = 0x43; 52 | pub static LDAP_ALREADY_EXISTS: i32 = 0x44; 53 | pub static LDAP_NO_OBJECT_CLASS_MODS: i32 = 0x45; 54 | pub static LDAP_RESULTS_TOO_LARGE: i32 = 0x46; 55 | pub static LDAP_AFFECTS_MULTIPLE_DSAS: i32 = 0x47; 56 | 57 | pub static LDAP_VLV_ERROR: i32 = 0x4c; 58 | 59 | pub static LDAP_OTHER: i32 = 0x50; 60 | } 61 | 62 | pub mod errors { 63 | /// Re-export api errors codes 64 | pub static LDAP_SERVER_DOWN: i32 = -1; 65 | pub static LDAP_LOCAL_ERROR: i32 = -2; 66 | pub static LDAP_ENCODING_ERROR: i32 = -3; 67 | pub static LDAP_DECODING_ERROR: i32 = -4; 68 | pub static LDAP_TIMEOUT: i32 = -5; 69 | pub static LDAP_AUTH_UNKNOWN: i32 = -6; 70 | pub static LDAP_FILTER_ERROR: i32 = -7; 71 | pub static LDAP_USER_CANCELLED: i32 = -8; 72 | pub static LDAP_PARAM_ERROR: i32 = -9; 73 | pub static LDAP_NO_MEMORY: i32 = -10; 74 | pub static LDAP_CONNECT_ERROR: i32 = -11; 75 | pub static LDAP_NOT_SUPPORTED: i32 = -12; 76 | pub static LDAP_CONTROL_NOT_FOUND: i32 = -13; 77 | pub static LDAP_NO_RESULTS_RETURNED: i32 = -14; 78 | pub static LDAP_MORE_RESULTS_TO_RETURN: i32 = -15; // Deprecated 79 | pub static LDAP_CLIENT_LOOP: i32 = -16; 80 | pub static LDAP_REFERRAL_LIMIT_EXCEEDED: i32 = -17; 81 | pub static LDAP_X_CONNECTING: i32 = -18; 82 | } 83 | 84 | pub mod filters { 85 | pub static LDAP_FILTER_AND: u32 = 0xa0; 86 | pub static LDAP_FILTER_OR: u32 = 0xa1; 87 | pub static LDAP_FILTER_NOT: u32 = 0xa2; 88 | pub static LDAP_FILTER_EQUALITY: u32 = 0xa3; 89 | pub static LDAP_FILTER_SUBSTRINGS: u32 = 0xa4; 90 | pub static LDAP_FILTER_GE: u32 = 0xa5; 91 | pub static LDAP_FILTER_LE: u32 = 0xa6; 92 | pub static LDAP_FILTER_PRESENT: u32 = 0x87; 93 | pub static LDAP_FILTER_APPROX: u32 = 0xa8; 94 | pub static LDAP_FILTER_EXT: u32 = 0xa9; 95 | 96 | pub static LDAP_FILTER_EXT_OID: u32 = 0x81; 97 | pub static LDAP_FILTER_EXT_TYPE: u32 = 0x82; 98 | pub static LDAP_FILTER_EXT_VALUE: u32 = 0x83; 99 | pub static LDAP_FILTER_EXT_DNATTRS: u32 = 0x84; 100 | 101 | pub static LDAP_SUBSTRING_INITIAL: u32 = 0x80; 102 | pub static LDAP_SUBSTRING_ANY: u32 = 0x81; 103 | pub static LDAP_SUBSTRING_FINAL: u32 = 0x82; 104 | } 105 | 106 | pub mod scopes { 107 | pub static LDAP_SCOPE_BASE: i32 = 0x0000; 108 | pub static LDAP_SCOPE_BASEOBJECT: i32 = 0x0000; 109 | pub static LDAP_SCOPE_ONELEVEL: i32 = 0x0001; 110 | pub static LDAP_SCOPE_ONE: i32 = 0x0001; 111 | pub static LDAP_SCOPE_SUBTREE: i32 = 0x0002; 112 | pub static LDAP_SCOPE_SUB: i32 = 0x0002; 113 | pub static LDAP_SCOPE_SUBORDINATE: i32 = 0x0003; 114 | pub static LDAP_SCOPE_CHILDREN: i32 = 0x0003; 115 | pub static LDAP_SCOPE_DEFAULT: i32 = -1; 116 | } 117 | 118 | pub mod versions { 119 | pub static LDAP_VERSION1: i32 = 1; 120 | pub static LDAP_VERSION2: i32 = 2; 121 | pub static LDAP_VERSION3: i32 = 3; 122 | } 123 | 124 | pub mod options { 125 | pub static LDAP_OPT_API_INFO: i32 = 0x0000; 126 | pub static LDAP_OPT_DESC: i32 = 0x0001; 127 | pub static LDAP_OPT_DEREF: i32 = 0x0002; 128 | pub static LDAP_OPT_SIZELIMIT: i32 = 0x0003; 129 | pub static LDAP_OPT_TIMELIMIT: i32 = 0x0004; 130 | /* 0x05 - 0x07 are undefined */ 131 | pub static LDAP_OPT_REFERRALS: i32 = 0x0008; 132 | pub static LDAP_OPT_RESTART: i32 = 0x0009; 133 | /* 0x0a - 0x10 are undefined */ 134 | pub static LDAP_OPT_PROTOCOL_VERSION: i32 = 0x0011; 135 | pub static LDAP_OPT_SERVER_CONTROLS: i32 = 0x0012; 136 | pub static LDAP_OPT_CLIENT_CONTROLS: i32 = 0x0013; 137 | /* 0x14 is undefined */ 138 | pub static LDAP_OPT_API_FEATURE_INFO: i32 = 0x0015; 139 | /* 0x16 - 0x2f are undefined */ 140 | pub static LDAP_OPT_HOST_NAME: i32 = 0x0030; 141 | pub static LDAP_OPT_RESULT_CODE: i32 = 0x0031; 142 | pub static LDAP_OPT_ERROR_NUMBER: i32 = 0x0031; 143 | pub static LDAP_OPT_DIAGNOSTIC_MESSAGE: i32 = 0x0032; 144 | pub static LDAP_OPT_ERROR_STRING: i32 = 0x0032; 145 | pub static LDAP_OPT_MATCHED_DN: i32 = 0x0033; 146 | /* 0x0034 - 0x3fff are undefined */ 147 | pub static LDAP_OPT_SSPI_FLAGS: i32 = 0x0092; 148 | pub static LDAP_OPT_SIGN: i32 = 0x0095; 149 | pub static LDAP_OPT_ENCRYPT: i32 = 0x0096; 150 | pub static LDAP_OPT_SASL_METHOD: i32 = 0x0097; 151 | pub static LDAP_OPT_SECURITY_CONTEXT: i32 = 0x0099; 152 | 153 | pub static LDAP_OPT_API_EXTENSION_BASE: i32 = 0x4000; 154 | 155 | pub static LDAP_OPT_X_TLS_CACERTDIR: i32 = 0x6003; 156 | pub static LDAP_OPT_X_TLS_CACERTFILE: i32 = 0x6002; 157 | pub static LDAP_OPT_X_TLS_CERTFILE: i32 = 0x6004; 158 | pub static LDAP_OPT_X_TLS_KEYFILE: i32 = 0x6005; 159 | pub static LDAP_OPT_X_TLS_NEWCTX: i32 = 0x600f; 160 | pub static LDAP_OPT_X_TLS_REQUIRE_CERT: i32 = 0x6006; 161 | 162 | pub static LDAP_OPT_X_TLS_NEVER: i32 = 0x0000; 163 | pub static LDAP_OPT_X_TLS_HARD: i32 = 0x0001; 164 | pub static LDAP_OPT_X_TLS_DEMAND: i32 = 0x0002; 165 | pub static LDAP_OPT_X_TLS_ALLOW: i32 = 0x0003; 166 | pub static LDAP_OPT_X_TLS_TRY: i32 = 0x0004; 167 | } 168 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Objects for connecting and querying LDAP servers using `OpenLDAP`. 2 | //! 3 | //! Current support includes connection, initializing, binding, configuring, and search against an 4 | //! LDAP directory. 5 | //! 6 | extern crate libc; 7 | use libc::{c_int, c_char, c_void, timeval}; 8 | use std::collections::HashMap; 9 | use std::ffi::{CStr, CString}; 10 | use std::ptr; 11 | use std::slice; 12 | use std::boxed; 13 | use std::ptr::null_mut; 14 | 15 | pub mod codes; 16 | pub mod errors; 17 | 18 | #[repr(C)] 19 | struct LDAP; 20 | 21 | #[repr(C)] 22 | struct LDAPMessage; 23 | 24 | #[repr(C)] 25 | pub struct LDAPControl; 26 | 27 | #[repr(C)] 28 | struct BerElement; 29 | 30 | unsafe impl Sync for LDAP {} 31 | unsafe impl Send for LDAP {} 32 | 33 | #[link(name = "lber")] 34 | #[allow(improper_ctypes)] 35 | extern "C" { 36 | fn ber_free(ber: *const BerElement, freebuf: c_int); 37 | } 38 | 39 | #[link(name = "ldap_r")] 40 | #[allow(improper_ctypes)] 41 | extern "C" { 42 | static ber_pvt_opt_on: c_char; 43 | fn ldap_initialize(ldap: *mut *mut LDAP, uri: *const c_char) -> c_int; 44 | fn ldap_memfree(p: *mut c_void); 45 | fn ldap_msgfree(msg: *mut LDAPMessage) -> c_int; 46 | fn ldap_err2string(err: c_int) -> *const c_char; 47 | fn ldap_first_entry(ldap: *mut LDAP, result: *mut LDAPMessage) -> *mut LDAPMessage; 48 | fn ldap_next_entry(ldap: *mut LDAP, entry: *mut LDAPMessage) -> *mut LDAPMessage; 49 | fn ldap_get_dn(ldap: *mut LDAP, entry: *mut LDAPMessage) -> *const c_char; 50 | fn ldap_get_values(ldap: *mut LDAP, 51 | entry: *mut LDAPMessage, 52 | attr: *const c_char) 53 | -> *const *const c_char; 54 | fn ldap_count_values(vals: *const *const c_char) -> c_int; 55 | fn ldap_value_free(vals: *const *const c_char); 56 | fn ldap_set_option(ldap: *const LDAP, option: c_int, invalue: *const c_void) -> c_int; 57 | fn ldap_simple_bind_s(ldap: *mut LDAP, who: *const c_char, pass: *const c_char) -> c_int; 58 | fn ldap_first_attribute(ldap: *mut LDAP, 59 | entry: *mut LDAPMessage, 60 | berptr: *mut *mut BerElement) 61 | -> *const c_char; 62 | fn ldap_next_attribute(ldap: *mut LDAP, 63 | entry: *mut LDAPMessage, 64 | berptr: *mut BerElement) 65 | -> *const c_char; 66 | fn ldap_search_ext_s(ldap: *mut LDAP, 67 | base: *const c_char, 68 | scope: c_int, 69 | filter: *const c_char, 70 | attrs: *const *const c_char, 71 | attrsonly: c_int, 72 | serverctrls: *mut *mut LDAPControl, 73 | clientctrls: *mut *mut LDAPControl, 74 | timeout: *mut timeval, 75 | sizelimit: c_int, 76 | res: *mut *mut LDAPMessage) 77 | -> c_int; 78 | fn ldap_unbind_ext_s(ldap: *mut LDAP, 79 | sctrls: *mut *mut LDAPControl, 80 | cctrls: *mut *mut LDAPControl) 81 | -> c_int; 82 | fn ldap_start_tls_s(ldap: *mut LDAP, 83 | scrtrls: *mut *mut LDAPControl, 84 | cctrls: *mut *mut LDAPControl) -> c_int; 85 | } 86 | 87 | /// A typedef for an `LDAPResponse` type. 88 | /// 89 | /// LDAP responses are organized as vectors of mached entities. Typically, each entity is 90 | /// represented as a map of attributes to list of values. 91 | /// 92 | pub type LDAPResponse = Vec>>; 93 | 94 | 95 | /// A high level abstraction over the raw `OpenLDAP` functions. 96 | /// 97 | /// A `RustLDAP` object hides raw `OpenLDAP` complexities and exposes a simple object that is 98 | /// created, configured, and queried. Methods that call underlying `OpenLDAP` calls that can fail 99 | /// will raise an `errors::LDAPError` with additional details. 100 | /// 101 | /// Using a `RustLDAP` object is easy! 102 | /// 103 | pub struct RustLDAP { 104 | /// A pointer to the underlying `OpenLDAP` object. 105 | ldap_ptr: *mut LDAP, 106 | } 107 | 108 | 109 | unsafe impl Sync for RustLDAP {} 110 | unsafe impl Send for RustLDAP {} 111 | 112 | impl Drop for RustLDAP { 113 | fn drop(&mut self) { 114 | // Unbind the LDAP connection, making the C library free the LDAP*. 115 | let rc = unsafe { ldap_unbind_ext_s(self.ldap_ptr, ptr::null_mut(), ptr::null_mut()) }; 116 | 117 | // Make sure it actually happened. 118 | if rc != codes::results::LDAP_SUCCESS { 119 | unsafe { 120 | // Hopefully this never happens. 121 | let raw_estr = ldap_err2string(rc as c_int); 122 | panic!(CStr::from_ptr(raw_estr).to_owned().into_string().unwrap()); 123 | } 124 | } 125 | } 126 | } 127 | 128 | /// A trait for types that can be passed as LDAP option values. 129 | /// 130 | /// Underlying `OpenLDAP` implementation calls for option values to be passed in as `*const c_void`, 131 | /// while allowing values to be `i32` or `String`. Using traits, we implement function overloading to 132 | /// handle `i32` and `String` option value types. 133 | /// 134 | /// This trait allocates memory that a caller must free using `std::boxed::Box::from_raw`. This 135 | /// helps guarantee that there is not a use after free bug (in Rust) while providing the appearance 136 | /// of opaque memory to `OpenLDAP` (in C). In pure C, we would've accomplished this by casting a 137 | /// local variable to a `const void *`. In Rust, we must do this on the heap to ensure Rust's 138 | /// ownership system does not free the memory used to store the option value between now and when 139 | /// the option is actually set. 140 | /// 141 | pub trait LDAPOptionValue { 142 | fn as_cvoid_ptr(&self) -> *const c_void; 143 | } 144 | 145 | impl LDAPOptionValue for str { 146 | fn as_cvoid_ptr(&self) -> *const c_void { 147 | let string = CString::new(self).unwrap(); 148 | string.into_raw() as *const c_void 149 | } 150 | } 151 | 152 | impl LDAPOptionValue for i32 { 153 | fn as_cvoid_ptr(&self) -> *const c_void { 154 | let mem = boxed::Box::new(*self); 155 | boxed::Box::into_raw(mem) as *const c_void 156 | } 157 | } 158 | 159 | impl LDAPOptionValue for bool { 160 | fn as_cvoid_ptr(&self) -> *const c_void { 161 | if *self { 162 | let mem = unsafe { boxed::Box::new(&ber_pvt_opt_on) }; 163 | boxed::Box::into_raw(mem) as *const c_void 164 | } else { 165 | let mem = boxed::Box::new(0); 166 | boxed::Box::into_raw(mem) as *const c_void 167 | } 168 | } 169 | } 170 | 171 | impl RustLDAP { 172 | /// Create a new `RustLDAP`. 173 | /// 174 | /// Creates a new `RustLDAP` and initializes underlying `OpenLDAP` library. Upon creation, a 175 | /// subsequent calls to `set_option` and `simple_bind` are possible. Before calling a search 176 | /// related function, one must bind to the server by calling `simple_bind`. See module usage 177 | /// information for more details on using a `RustLDAP` object. 178 | /// 179 | /// # Parameters 180 | /// 181 | /// * uri - URI of the LDAP server to connect to. E.g., ldaps://localhost:636. 182 | /// 183 | pub fn new(uri: &str) -> Result { 184 | 185 | // Create some space for the LDAP pointer. 186 | let mut cldap = ptr::null_mut(); 187 | 188 | let uri_cstring = CString::new(uri).unwrap(); 189 | 190 | unsafe { 191 | let res = ldap_initialize(&mut cldap, uri_cstring.as_ptr()); 192 | if res != codes::results::LDAP_SUCCESS { 193 | let raw_estr = ldap_err2string(res as c_int); 194 | return Err(errors::LDAPError::NativeError(CStr::from_ptr(raw_estr) 195 | .to_owned() 196 | .into_string() 197 | .unwrap())); 198 | } 199 | 200 | } 201 | 202 | Ok(RustLDAP { ldap_ptr: cldap }) 203 | } 204 | 205 | /// Sets an option on the LDAP connection. 206 | /// 207 | /// When setting an option to _ON_ or _OFF_ one may use the boolean values `true` or `false`, 208 | /// respectively. 209 | /// 210 | /// # Parameters 211 | /// 212 | /// * option - An option identifier from `cldap::codes`. 213 | /// * value - The value to set for the option. 214 | /// 215 | pub fn set_option(&self, option: i32, value: &T) -> bool { 216 | let ptr: *const c_void = value.as_cvoid_ptr(); 217 | unsafe { 218 | let res: i32; 219 | res = ldap_set_option(self.ldap_ptr, option, ptr); 220 | // Allows for memory to be dropped when this binding goes away. 221 | let _ = boxed::Box::from_raw(ptr as *mut c_void); 222 | res == 0 223 | } 224 | } 225 | 226 | /// Bind to the LDAP server. 227 | /// 228 | /// If you wish to configure options on the LDAP server, be sure to set required options using 229 | ///`set_option` _before_ binding to the LDAP server. In some advanced cases, it may be required 230 | /// to set multiple options for an option to be made available. Refer to the `OpenLDAP` 231 | /// documentation for information on available options and how to use them. 232 | /// 233 | /// # Parameters 234 | /// 235 | /// * who - The user's name to bind with. 236 | /// * pass - The user's password to bind with. 237 | /// 238 | pub fn simple_bind(&self, who: &str, pass: &str) -> Result { 239 | let who_cstr = CString::new(who).unwrap(); 240 | let pass_cstr = CString::new(pass).unwrap(); 241 | let who_ptr = who_cstr.as_ptr(); 242 | let pass_ptr = pass_cstr.as_ptr(); 243 | unsafe { 244 | let res = ldap_simple_bind_s(self.ldap_ptr, who_ptr, pass_ptr); 245 | if res < 0 { 246 | let raw_estr = ldap_err2string(res as c_int); 247 | return Err(errors::LDAPError::NativeError(CStr::from_ptr(raw_estr) 248 | .to_owned() 249 | .into_string() 250 | .unwrap())); 251 | } 252 | Ok(res) 253 | } 254 | } 255 | 256 | /// Simple synchronous search. 257 | /// 258 | /// Performs a simple search with only the base, returning all attributes found. 259 | /// 260 | /// # Parameters 261 | /// 262 | /// * base - The LDAP base. 263 | /// * scope - The search scope. See `cldap::codes::scopes`. 264 | /// 265 | pub fn simple_search(&self, base: &str, scope: i32) -> Result { 266 | self.ldap_search(base, 267 | scope, 268 | None, 269 | None, 270 | false, 271 | None, 272 | None, 273 | ptr::null_mut(), 274 | -1) 275 | } 276 | 277 | /// Installs TLS handlers on the session 278 | /// 279 | /// # Examples 280 | /// 281 | /// ```should_panic 282 | /// use openldap::RustLDAP; 283 | /// let ldap = RustLDAP::new(&"ldaps://myserver:636").unwrap(); 284 | /// 285 | /// ldap.set_option( 286 | /// openldap::codes::options::LDAP_OPT_PROTOCOL_VERSION, 287 | /// &openldap::codes::versions::LDAP_VERSION3, 288 | /// ); 289 | /// 290 | /// ldap.set_option( 291 | /// openldap::codes::options::LDAP_OPT_X_TLS_REQUIRE_CERT, 292 | /// &openldap::codes::options::LDAP_OPT_X_TLS_ALLOW, 293 | /// ); 294 | /// 295 | /// ldap.set_option(openldap::codes::options::LDAP_OPT_X_TLS_NEWCTX, &0); 296 | /// 297 | /// ldap.start_tls(None, None); 298 | /// ldap.simple_bind("some-dn", "some-password").unwrap(); 299 | /// ``` 300 | pub fn start_tls(&self, serverctrls: Option<*mut *mut LDAPControl>, clientctrls: Option<*mut *mut LDAPControl>) -> Result { 301 | let r_serverctrls = match serverctrls { 302 | Some(sc) => sc, 303 | None => ptr::null_mut(), 304 | }; 305 | 306 | let r_clientctrls = match clientctrls { 307 | Some(cc) => cc, 308 | None => ptr::null_mut(), 309 | }; 310 | 311 | unsafe { 312 | let res = ldap_start_tls_s(self.ldap_ptr, r_serverctrls, r_clientctrls); 313 | 314 | if res < 0 { 315 | let raw_estr = ldap_err2string(res as c_int); 316 | return Err(errors::LDAPError::NativeError(CStr::from_ptr(raw_estr) 317 | .to_owned() 318 | .into_string() 319 | .unwrap())); 320 | } 321 | 322 | Ok(res) 323 | } 324 | } 325 | 326 | /// Advanced synchronous search. 327 | /// 328 | /// Exposes a raw API around the underlying `ldap_search_ext_s` function from `OpenLDAP`. 329 | /// Wherever possible, use provided wrappers. 330 | /// 331 | /// # Parameters 332 | /// 333 | /// * base - The base domain. 334 | /// * scope - The search scope. See `cldap::codes::scopes`. 335 | /// * filter - An optional filter. 336 | /// * attrs - An optional set of attrs. 337 | /// * attrsonly - True if should return only the attrs specified in `attrs`. 338 | /// * serverctrls - Optional sever controls. 339 | /// * clientctrls - Optional client controls. 340 | /// * timeout - A timeout. 341 | /// * sizelimit - The maximum number of entities to return, or -1 for no limit. 342 | /// 343 | pub fn ldap_search(&self, 344 | base: &str, 345 | scope: i32, 346 | filter: Option<&str>, 347 | attrs: Option>, 348 | attrsonly: bool, 349 | serverctrls: Option<*mut *mut LDAPControl>, 350 | clientctrls: Option<*mut *mut LDAPControl>, 351 | timeout: *mut timeval, 352 | sizelimit: i32) 353 | -> Result { 354 | 355 | // Make room for the LDAPMessage, being sure to delete this before we return. 356 | let mut ldap_msg = ptr::null_mut(); 357 | 358 | // Convert the passed in filter sting to either a C-string or null if one is not passed. 359 | let filter_cstr: CString; 360 | let r_filter = match filter { 361 | Some(fs) => { 362 | filter_cstr = CString::new(fs).unwrap(); 363 | filter_cstr.as_ptr() 364 | } 365 | None => ptr::null(), 366 | }; 367 | 368 | // Convert the vec of attributes into the null-terminated array that the library expects. 369 | let mut r_attrs: *const *const c_char = ptr::null(); 370 | let mut c_strs: Vec = Vec::new(); 371 | let mut r_attrs_ptrs: Vec<*const c_char> = Vec::new(); 372 | 373 | if let Some(strs) = attrs { 374 | for string in strs { 375 | // Create new CString and take ownership of it in c_strs. 376 | c_strs.push(CString::new(string).unwrap()); 377 | // Create a pointer to that CString's raw data and store it in r_attrs. 378 | r_attrs_ptrs.push(c_strs[c_strs.len() - 1].as_ptr()); 379 | } 380 | // Ensure that there is a null value at the end of the vector. 381 | r_attrs_ptrs.push(ptr::null()); 382 | r_attrs = r_attrs_ptrs.as_ptr(); 383 | } 384 | 385 | let r_serverctrls = match serverctrls { 386 | Some(sc) => sc, 387 | None => ptr::null_mut(), 388 | }; 389 | 390 | let r_clientctrls = match clientctrls { 391 | Some(cc) => cc, 392 | None => ptr::null_mut(), 393 | }; 394 | 395 | let base = CString::new(base).unwrap(); 396 | 397 | unsafe { 398 | let res: i32 = ldap_search_ext_s(self.ldap_ptr, 399 | base.as_ptr(), 400 | scope as c_int, 401 | r_filter, 402 | r_attrs, 403 | attrsonly as c_int, 404 | r_serverctrls, 405 | r_clientctrls, 406 | timeout, 407 | sizelimit as c_int, 408 | &mut ldap_msg); 409 | if res != codes::results::LDAP_SUCCESS { 410 | let raw_estr = ldap_err2string(res as c_int); 411 | return Err(errors::LDAPError::NativeError(CStr::from_ptr(raw_estr) 412 | .to_owned() 413 | .into_string() 414 | .unwrap())); 415 | } 416 | } 417 | 418 | // We now have to parse the results, copying the C-strings into Rust ones making sure to 419 | // free the C-strings afterwards 420 | let mut resvec: Vec>> = vec![]; 421 | let mut entry = unsafe { ldap_first_entry(self.ldap_ptr, ldap_msg) }; 422 | 423 | while !entry.is_null() { 424 | 425 | // Make the map holding the attribute : value pairs as well as the BerElement that keeps 426 | // track of what position we're in 427 | let mut map: HashMap> = HashMap::new(); 428 | let mut ber: *mut BerElement = ptr::null_mut(); 429 | unsafe { 430 | // Populate the "DN" of the user 431 | let raw_dn = ldap_get_dn(self.ldap_ptr, entry); 432 | let mut dn: Vec = Vec::new(); 433 | dn.push(CStr::from_ptr(raw_dn) 434 | .to_owned() 435 | .into_string() 436 | .unwrap_or("".to_string())); 437 | map.insert("dn".to_string(), dn); 438 | ldap_memfree(raw_dn as *mut c_void); 439 | 440 | let mut attr: *const c_char = ldap_first_attribute(self.ldap_ptr, entry, &mut ber); 441 | 442 | while !attr.is_null() { 443 | 444 | // Convert the attribute into a Rust string. 445 | let key = CStr::from_ptr(attr).to_owned().into_string().unwrap(); 446 | 447 | // Get the attribute values from LDAP. 448 | let raw_vals: *const *const c_char = 449 | ldap_get_values(self.ldap_ptr, entry, attr); 450 | let raw_vals_len = ldap_count_values(raw_vals) as usize; 451 | let val_slice: &[*const c_char] = slice::from_raw_parts(raw_vals, raw_vals_len); 452 | 453 | // Map these into a vector of Strings. 454 | let values: Vec = val_slice.iter() 455 | .map(|ptr| { 456 | // TODO(sholsapp): If this contains binary data this will fail. 457 | CStr::from_ptr(*ptr) 458 | .to_owned() 459 | .into_string() 460 | .unwrap_or("".to_string()) 461 | }) 462 | .collect(); 463 | 464 | // Insert newly constructed Rust key-value strings. 465 | map.insert(key, values); 466 | 467 | // Free the attr and value, then get next attr. 468 | ldap_value_free(raw_vals); 469 | ldap_memfree(attr as *mut c_void); 470 | attr = ldap_next_attribute(self.ldap_ptr, entry, ber) 471 | } 472 | 473 | // Free the BerElement and advance to the next entry. 474 | ber_free(ber, 0); 475 | entry = ldap_next_entry(self.ldap_ptr, entry); 476 | 477 | } 478 | 479 | // Push this entry into the vector. 480 | resvec.push(map); 481 | 482 | } 483 | 484 | // Make sure we free the message and return the parsed results. 485 | unsafe { ldap_msgfree(ldap_msg) }; 486 | 487 | Ok(resvec) 488 | } 489 | } 490 | 491 | #[cfg(test)] 492 | mod tests { 493 | 494 | use std::ptr; 495 | use codes; 496 | 497 | const TEST_ADDRESS: &'static str = "ldap://ldap.forumsys.com"; 498 | const TEST_BIND_DN: &'static str = "cn=read-only-admin,dc=example,dc=com"; 499 | const TEST_BIND_PASS: &'static str = "password"; 500 | const TEST_SIMPLE_SEARCH_QUERY: &'static str = "uid=tesla,dc=example,dc=com"; 501 | const TEST_SEARCH_BASE: &'static str = "dc=example,dc=com"; 502 | const TEST_SEARCH_FILTER: &'static str = "(uid=euler)"; 503 | const TEST_SEARCH_INVALID_FILTER: &'static str = "(uid=INVALID)"; 504 | 505 | /// Test creating a RustLDAP struct with a valid uri. 506 | #[test] 507 | fn test_ldap_new() { 508 | let _ = super::RustLDAP::new(TEST_ADDRESS).unwrap(); 509 | } 510 | 511 | /// Test creating a RustLDAP struct with an invalid uri. 512 | #[test] 513 | fn test_invalid_ldap_new() { 514 | if let Err(e) = super::RustLDAP::new("lda://localhost") { 515 | assert_eq!(super::errors::LDAPError::NativeError("Bad parameter to an ldap routine" 516 | .to_string()), 517 | e); 518 | } else { 519 | assert!(false); 520 | } 521 | } 522 | 523 | #[test] 524 | #[should_panic] 525 | fn test_invalid_cstring_ldap_new() { 526 | let _ = super::RustLDAP::new("INVALID\0CSTRING").unwrap(); 527 | } 528 | 529 | #[test] 530 | fn test_simple_bind() { 531 | let ldap = super::RustLDAP::new(TEST_ADDRESS).unwrap(); 532 | assert!(ldap.set_option(codes::options::LDAP_OPT_PROTOCOL_VERSION, &3)); 533 | let res = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).unwrap(); 534 | assert_eq!(codes::results::LDAP_SUCCESS, res); 535 | println!("Bind result: {:?}", res); 536 | 537 | } 538 | 539 | #[test] 540 | #[should_panic] // the TEST_ADDRESS being used doesn't support LDAPS, only LDAP 541 | fn test_simple_bind_with_start_tls() { 542 | let ldap = super::RustLDAP::new(TEST_ADDRESS).unwrap(); 543 | 544 | assert!(ldap.set_option(codes::options::LDAP_OPT_PROTOCOL_VERSION, &3)); 545 | ldap.start_tls(None, None); 546 | 547 | ldap.set_option( 548 | codes::options::LDAP_OPT_PROTOCOL_VERSION, 549 | &codes::versions::LDAP_VERSION3, 550 | ); 551 | 552 | ldap.set_option( 553 | codes::options::LDAP_OPT_X_TLS_REQUIRE_CERT, 554 | &codes::options::LDAP_OPT_X_TLS_ALLOW, 555 | ); 556 | 557 | ldap.set_option(codes::options::LDAP_OPT_X_TLS_NEWCTX, &0); 558 | 559 | let res = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).unwrap(); 560 | assert_eq!(codes::results::LDAP_SUCCESS, res); 561 | println!("Bind result: {:?}", res); 562 | 563 | } 564 | 565 | 566 | #[test] 567 | fn test_simple_search() { 568 | 569 | println!("Testing simple search"); 570 | let ldap = super::RustLDAP::new(TEST_ADDRESS).unwrap(); 571 | assert!(ldap.set_option(codes::options::LDAP_OPT_PROTOCOL_VERSION, &3)); 572 | let res = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).unwrap(); 573 | assert_eq!(codes::results::LDAP_SUCCESS, res); 574 | let search_res = 575 | ldap.simple_search(TEST_SIMPLE_SEARCH_QUERY, codes::scopes::LDAP_SCOPE_BASE).unwrap(); 576 | 577 | //make sure we got something back 578 | assert!(search_res.len() == 1); 579 | 580 | // make sure the DN is searched and returned correctly 581 | assert_eq!(search_res[0]["dn"][0], "uid=tesla,dc=example,dc=com"); 582 | 583 | for result in search_res { 584 | println!("simple search result: {:?}", result); 585 | for (key, value) in result { 586 | println!("- key: {:?}", key); 587 | for res_val in value { 588 | println!("- - res_val: {:?}", res_val); 589 | } 590 | } 591 | } 592 | 593 | } 594 | 595 | #[test] 596 | fn test_search() { 597 | 598 | println!("Testing search"); 599 | let ldap = super::RustLDAP::new(TEST_ADDRESS).unwrap(); 600 | assert!(ldap.set_option(codes::options::LDAP_OPT_PROTOCOL_VERSION, &3)); 601 | let res = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).unwrap(); 602 | assert_eq!(codes::results::LDAP_SUCCESS, res); 603 | let search_res = ldap.ldap_search(TEST_SEARCH_BASE, 604 | codes::scopes::LDAP_SCOPE_SUB, 605 | Some(TEST_SEARCH_FILTER), 606 | None, 607 | false, 608 | None, 609 | None, 610 | ptr::null_mut(), 611 | -1) 612 | .unwrap(); 613 | 614 | //make sure we got something back 615 | assert!(search_res.len() == 1); 616 | 617 | // make sure the DN is searched and returned correctly 618 | assert_eq!(search_res[0]["dn"][0], "uid=euler,dc=example,dc=com"); 619 | 620 | for result in search_res { 621 | println!("search result: {:?}", result); 622 | for (key, value) in result { 623 | println!("- key: {:?}", key); 624 | for res_val in value { 625 | println!("- - res_val: {:?}", res_val); 626 | } 627 | } 628 | } 629 | 630 | } 631 | 632 | #[test] 633 | fn test_invalid_search() { 634 | 635 | println!("Testing invalid search"); 636 | let ldap = super::RustLDAP::new(TEST_ADDRESS).unwrap(); 637 | assert!(ldap.set_option(codes::options::LDAP_OPT_PROTOCOL_VERSION, &3)); 638 | let res = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).unwrap(); 639 | assert_eq!(codes::results::LDAP_SUCCESS, res); 640 | let search_res = ldap.ldap_search(TEST_SEARCH_BASE, 641 | codes::scopes::LDAP_SCOPE_SUB, 642 | Some(TEST_SEARCH_INVALID_FILTER), 643 | None, 644 | false, 645 | None, 646 | None, 647 | ptr::null_mut(), 648 | -1) 649 | .unwrap(); 650 | 651 | //make sure we got something back 652 | assert!(search_res.len() == 0); 653 | 654 | } 655 | 656 | #[test] 657 | fn test_search_attrs() { 658 | 659 | println!("Testing search with attrs"); 660 | let test_search_attrs_vec = vec!["cn", "sn", "mail"]; 661 | let ldap = super::RustLDAP::new(TEST_ADDRESS).unwrap(); 662 | assert!(ldap.set_option(codes::options::LDAP_OPT_PROTOCOL_VERSION, &3)); 663 | let res = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).unwrap(); 664 | assert_eq!(codes::results::LDAP_SUCCESS, res); 665 | let search_res = ldap.ldap_search(TEST_SEARCH_BASE, 666 | codes::scopes::LDAP_SCOPE_SUB, 667 | Some(TEST_SEARCH_FILTER), 668 | Some(test_search_attrs_vec), 669 | false, 670 | None, 671 | None, 672 | ptr::null_mut(), 673 | -1) 674 | .unwrap(); 675 | 676 | //make sure we got something back 677 | assert!(search_res.len() == 1); 678 | 679 | for result in search_res { 680 | println!("attrs search result: {:?}", result); 681 | for (key, value) in result { 682 | println!("- key: {:?}", key); 683 | for res_val in value { 684 | println!("- - res_val: {:?}", res_val); 685 | } 686 | } 687 | } 688 | 689 | } 690 | 691 | #[test] 692 | fn test_search_invalid_attrs() { 693 | 694 | println!("Testing search with invalid attrs"); 695 | let test_search_attrs_vec = vec!["cn", "sn", "mail", "INVALID"]; 696 | let ldap = super::RustLDAP::new(TEST_ADDRESS).unwrap(); 697 | assert!(ldap.set_option(codes::options::LDAP_OPT_PROTOCOL_VERSION, &3)); 698 | let res = ldap.simple_bind(TEST_BIND_DN, TEST_BIND_PASS).unwrap(); 699 | assert_eq!(codes::results::LDAP_SUCCESS, res); 700 | let search_res = ldap.ldap_search(TEST_SEARCH_BASE, 701 | codes::scopes::LDAP_SCOPE_SUB, 702 | Some(TEST_SEARCH_FILTER), 703 | Some(test_search_attrs_vec), 704 | false, 705 | None, 706 | None, 707 | ptr::null_mut(), 708 | -1) 709 | .unwrap(); 710 | 711 | for result in search_res { 712 | println!("attrs search result: {:?}", result); 713 | for (key, value) in result { 714 | println!("- key: {:?}", key); 715 | for res_val in value { 716 | println!("- - res_val: {:?}", res_val); 717 | } 718 | } 719 | } 720 | 721 | } 722 | 723 | } 724 | --------------------------------------------------------------------------------