#[cfg(all(unix, not(feature = "minimal")))]
use std::borrow::Borrow;
use std::cell::RefCell;
use std::collections::HashSet;
use std::convert::AsRef;
use std::hash::Hash;
use std::io;
use std::marker::PhantomData;
use std::mem;
use std::net::{IpAddr, SocketAddr, ToSocketAddrs};
use std::rc::Rc;
use std::time::Duration;
use futures::{Async, Future, Poll, Stream};
use futures::sync::oneshot;
#[cfg(feature = "tls")]
use native_tls::TlsConnector;
use tokio_core::reactor::{Core, Handle};
use url::{Host, Url};
#[cfg(all(unix, not(feature = "minimal")))]
use url::percent_encoding::percent_decode;
use controls_impl::IntoRawControlVec;
use exop::Exop;
#[cfg(feature = "tls")]
use ldap::connect_with_tls_connector;
use ldap::Ldap;
use modify::Mod;
use result::{CompareResult, ExopResult, LdapResult, SearchResult};
use search::{ResultEntry, SearchOptions, SearchStream, Scope};
struct LdapWrapper {
inner: Ldap,
}
impl LdapWrapper {
fn ldap(&self) -> Ldap {
self.inner.clone()
}
fn connect(addr: &SocketAddr, handle: &Handle, timeout: Option<Duration>)
-> Box<Future<Item=LdapWrapper, Error=io::Error>>
{
let lw = Ldap::connect(addr, handle, timeout)
.map(|ldap| {
LdapWrapper {
inner: ldap,
}
});
Box::new(lw)
}
#[cfg(feature = "tls")]
fn connect_ssl(addr: &str, handle: &Handle, timeout: Option<Duration>, connector: Option<TlsConnector>)
-> Box<Future<Item=LdapWrapper, Error=io::Error>>
{
let lw = connect_with_tls_connector(addr, handle, timeout, connector)
.map(|ldap| {
LdapWrapper {
inner: ldap,
}
});
Box::new(lw)
}
#[cfg(all(unix, not(feature = "minimal")))]
fn connect_unix(path: &str, handle: &Handle) -> Box<Future<Item=LdapWrapper, Error=io::Error>> {
let lw = Ldap::connect_unix(path, handle)
.map(|ldap| {
LdapWrapper {
inner: ldap,
}
});
Box::new(lw)
}
}
pub struct EntryStream {
core: Rc<RefCell<Core>>,
strm: Option<SearchStream>,
rx_r: Option<oneshot::Receiver<LdapResult>>,
}
impl EntryStream {
#[cfg_attr(feature="cargo-clippy", allow(should_implement_trait))]
pub fn next(&mut self) -> io::Result<Option<ResultEntry>> {
let strm = self.strm.take();
if strm.is_none() {
return Err(io::Error::new(io::ErrorKind::Other, "cannot fetch from an invalid stream"));
}
let (tag, strm) = self.core.borrow_mut().run(strm.expect("stream").into_future()).map_err(|e| e.0)?;
self.strm = Some(strm);
Ok(tag)
}
pub fn result(&mut self) -> io::Result<LdapResult> {
if self.strm.is_none() {
return Err(io::Error::new(io::ErrorKind::Other, "cannot return result from an invalid stream"));
}
let rx_r = self.rx_r.take().expect("oneshot rx");
let res = self.core.borrow_mut().run(rx_r).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
Ok(res)
}
pub fn abandon(&mut self) -> io::Result<()> {
if let Some(mut strm) = self.strm.take() {
let channel = strm.get_abandon_channel()?;
self.strm = Some(strm);
Ok(channel.send(()).map_err(|_e| io::Error::new(io::ErrorKind::Other, "send on abandon channel"))?)
} else {
Err(io::Error::new(io::ErrorKind::Other, "cannot abandon an invalid stream"))
}
}
}
#[derive(Clone)]
pub struct LdapConn {
core: Rc<RefCell<Core>>,
inner: Ldap,
}
impl LdapConn {
pub fn new(url: &str) -> io::Result<Self> {
LdapConn::with_settings(LdapConnSettings::new(), url)
}
fn with_settings(settings: LdapConnSettings, url: &str) -> io::Result<Self> {
let mut core = Core::new()?;
let conn = LdapConnAsync::with_settings(settings, url, &core.handle())?;
let ldap = core.run(conn)?;
Ok(LdapConn {
core: Rc::new(RefCell::new(core)),
inner: ldap,
})
}
pub fn simple_bind(&self, bind_dn: &str, bind_pw: &str) -> io::Result<LdapResult> {
Ok(self.core.borrow_mut().run(self.inner.clone().simple_bind(bind_dn, bind_pw))?)
}
#[cfg(all(unix, not(feature = "minimal")))]
pub fn sasl_external_bind(&self) -> io::Result<LdapResult> {
Ok(self.core.borrow_mut().run(self.inner.clone().sasl_external_bind())?)
}
pub fn with_search_options(&self, opts: SearchOptions) -> &Self {
self.inner.with_search_options(opts);
self
}
pub fn with_controls<V: IntoRawControlVec>(&self, ctrls: V) -> &Self {
self.inner.with_controls(ctrls);
self
}
pub fn with_timeout(&self, duration: Duration) -> &Self {
self.inner.with_timeout(duration);
self
}
pub fn search<S: AsRef<str>>(&self, base: &str, scope: Scope, filter: &str, attrs: Vec<S>) -> io::Result<SearchResult> {
let srch = self.inner.clone().search(base, scope, filter, attrs);
Ok(self.core.borrow_mut().run(srch)?)
}
pub fn streaming_search<S: AsRef<str>>(&self, base: &str, scope: Scope, filter: &str, attrs: Vec<S>) -> io::Result<EntryStream> {
let mut strm = self.core.borrow_mut().run(self.inner.clone().streaming_search(base, scope, filter, attrs))?;
let rx_r = strm.get_result_rx()?;
Ok(EntryStream { core: self.core.clone(), strm: Some(strm), rx_r: Some(rx_r) })
}
pub fn add<S: AsRef<str> + Eq + Hash>(&self, dn: &str, attrs: Vec<(S, HashSet<S>)>) -> io::Result<LdapResult> {
Ok(self.core.borrow_mut().run(self.inner.clone().add(dn, attrs))?)
}
pub fn delete(&self, dn: &str) -> io::Result<LdapResult> {
Ok(self.core.borrow_mut().run(self.inner.clone().delete(dn))?)
}
pub fn modify<S: AsRef<str> + Eq + Hash>(&self, dn: &str, mods: Vec<Mod<S>>) -> io::Result<LdapResult> {
Ok(self.core.borrow_mut().run(self.inner.clone().modify(dn, mods))?)
}
pub fn modifydn(&self, dn: &str, rdn: &str, delete_old: bool, new_sup: Option<&str>) -> io::Result<LdapResult> {
Ok(self.core.borrow_mut().run(self.inner.clone().modifydn(dn, rdn, delete_old, new_sup))?)
}
pub fn unbind(&self) -> io::Result<()> {
Ok(self.core.borrow_mut().run(self.inner.clone().unbind())?)
}
pub fn compare<B: AsRef<[u8]>>(&self, dn: &str, attr: &str, val: B) -> io::Result<CompareResult> {
Ok(self.core.borrow_mut().run(self.inner.clone().compare(dn, attr, val))?)
}
pub fn extended<E>(&self, exop: E) -> io::Result<ExopResult>
where E: Into<Exop>
{
Ok(self.core.borrow_mut().run(self.inner.clone().extended(exop))?)
}
}
#[derive(Clone)]
pub struct LdapConnAsync {
in_progress: Rc<RefCell<Box<Future<Item=LdapWrapper, Error=io::Error>>>>,
wrapper: Rc<RefCell<Option<LdapWrapper>>>,
}
impl LdapConnAsync {
#[cfg(any(not(unix), feature = "minimal"))]
pub fn new(url: &str, handle: &Handle) -> io::Result<Self> {
LdapConnAsync::new_tcp(url, handle, LdapConnSettings::new())
}
#[cfg(any(not(unix), feature = "minimal"))]
fn with_settings(settings: LdapConnSettings, url: &str, handle: &Handle) -> io::Result<Self> {
LdapConnAsync::new_tcp(url, handle, settings)
}
#[cfg(all(unix, not(feature = "minimal")))]
pub fn new(url: &str, handle: &Handle) -> io::Result<Self> {
if !url.starts_with("ldapi://") {
LdapConnAsync::new_tcp(url, handle, LdapConnSettings::new())
} else {
LdapConnAsync::new_unix(url, handle)
}
}
#[cfg(all(unix, not(feature = "minimal")))]
fn with_settings(settings: LdapConnSettings, url: &str, handle: &Handle) -> io::Result<Self> {
if !url.starts_with("ldapi://") {
LdapConnAsync::new_tcp(url, handle, settings)
} else {
LdapConnAsync::new_unix(url, handle)
}
}
#[cfg(all(unix, not(feature = "minimal")))]
fn new_unix(url: &str, handle: &Handle) -> io::Result<Self> {
let path = url.split('/').nth(2).unwrap();
if path.is_empty() {
return Err(io::Error::new(io::ErrorKind::Other, "empty Unix domain socket path"));
}
if path.contains(':') {
return Err(io::Error::new(io::ErrorKind::Other, "the port must be empty in the ldapi scheme"));
}
let dec_path = percent_decode(path.as_bytes()).decode_utf8_lossy();
Ok(LdapConnAsync {
in_progress: Rc::new(RefCell::new(LdapWrapper::connect_unix(dec_path.borrow(), handle))),
wrapper: Rc::new(RefCell::new(None)),
})
}
fn new_tcp(url: &str, handle: &Handle, settings: LdapConnSettings) -> io::Result<Self> {
let url = Url::parse(url).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
let mut port = 389;
let scheme = match url.scheme() {
s @ "ldap" => s,
#[cfg(feature = "tls")]
s @ "ldaps" => { port = 636; s },
s => return Err(io::Error::new(io::ErrorKind::Other, format!("unimplemented LDAP URL scheme: {}", s))),
};
if let Some(url_port) = url.port() {
port = url_port;
}
let host_port = match url.host_str() {
Some(h) => format!("{}:{}", h, port),
None => format!("localhost:{}", port),
};
let addr = match url.host() {
Some(Host::Ipv4(v4)) if scheme == "ldap" => Some(SocketAddr::new(IpAddr::V4(v4), port)),
Some(Host::Ipv6(v6)) if scheme == "ldap" => Some(SocketAddr::new(IpAddr::V6(v6), port)),
Some(Host::Domain(_)) if scheme == "ldap" => {
match host_port.to_socket_addrs() {
Ok(mut addrs) => match addrs.next() {
Some(addr) => Some(addr),
None => return Err(io::Error::new(io::ErrorKind::Other, format!("empty address list for: {}", host_port))),
},
Err(e) => return Err(e),
}
}
_ => None,
};
Ok(LdapConnAsync {
in_progress: match scheme {
"ldap" => Rc::new(RefCell::new(LdapWrapper::connect(&addr.expect("addr"), handle, settings.timeout))),
#[cfg(feature = "tls")]
"ldaps" => Rc::new(RefCell::new(LdapWrapper::connect_ssl(&host_port, handle, settings.timeout, settings.connector))),
_ => unimplemented!(),
},
wrapper: Rc::new(RefCell::new(None)),
})
}
}
impl Future for LdapConnAsync {
type Item = Ldap;
type Error = io::Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(ref wrapper) = *RefCell::borrow(&self.wrapper) {
return Ok(Async::Ready(wrapper.ldap()));
}
match self.in_progress.borrow_mut().poll() {
Ok(Async::Ready(wrapper)) => {
let ldap = wrapper.ldap();
mem::replace(&mut *self.wrapper.borrow_mut(), Some(wrapper));
Ok(Async::Ready(ldap))
},
Ok(Async::NotReady) => Ok(Async::NotReady),
Err(e) => Err(e),
}
}
}
pub struct LdapConnBuilder<T> {
settings: LdapConnSettings,
_marker: PhantomData<T>,
}
impl<T> LdapConnBuilder<T> {
pub fn with_conn_timeout(mut self, timeout: Duration) -> Self {
self.settings = self.settings.set_timeout(Some(timeout));
self
}
#[cfg(feature = "tls")]
#[doc(hidden)]
pub fn with_tls_connector(mut self, connector: TlsConnector) -> Self {
self.settings = self.settings.set_connector(Some(connector));
self
}
}
impl LdapConnBuilder<LdapConn> {
pub fn new() -> LdapConnBuilder<LdapConn> {
LdapConnBuilder {
settings: LdapConnSettings::new(),
_marker: PhantomData,
}
}
pub fn connect(self, url: &str) -> io::Result<LdapConn> {
LdapConn::with_settings(self.settings, url)
}
}
impl LdapConnBuilder<LdapConnAsync> {
pub fn new() -> LdapConnBuilder<LdapConnAsync> {
LdapConnBuilder {
settings: LdapConnSettings::new(),
_marker: PhantomData,
}
}
pub fn connect(self, url: &str, handle: &Handle) -> io::Result<LdapConnAsync> {
LdapConnAsync::with_settings(self.settings, url, handle)
}
}
#[derive(Default)]
struct LdapConnSettings {
timeout: Option<Duration>,
#[cfg(feature = "tls")]
connector: Option<TlsConnector>,
}
impl LdapConnSettings {
fn new() -> LdapConnSettings {
LdapConnSettings {
..Default::default()
}
}
fn set_timeout(mut self, timeout: Option<Duration>) -> Self {
self.timeout = timeout;
self
}
#[cfg(feature = "tls")]
fn set_connector(mut self, connector: Option<TlsConnector>) -> Self {
self.connector = connector;
self
}
}