Skip to content

Commit

Permalink
Add support for opening a connection from an existing stream
Browse files Browse the repository at this point in the history
Rework of an earlier PR, so that a stdlib stream can be placed in
LdapConnSettings and used to initialize the connection. To keep
the settings cloneable, the stream-bearing enum is cloned to an
invalid variant, since the streams themselves can't be cloned.

The provided stream is automatically set to nonblocking mode.
  • Loading branch information
inejge committed Oct 23, 2024
1 parent d3147ff commit 504d45a
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 12 deletions.
17 changes: 17 additions & 0 deletions examples/bind_sync_tcp_stream.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Demonstrates synchronously connecting, binding to,
// and disconnecting from the exiting tcp stream.

use std::net::TcpStream;

use ldap3::result::Result;
use ldap3::{LdapConn, LdapConnSettings, StdStream};

fn main() -> Result<()> {
let stream = TcpStream::connect("localhost:2389")?;
let settings = LdapConnSettings::new().set_std_stream(StdStream::Tcp(stream));
let mut ldap = LdapConn::with_settings(settings, "ldap://localhost:2389")?;
let _res = ldap
.simple_bind("cn=Manager,dc=example,dc=org", "secret")?
.success()?;
Ok(ldap.unbind()?)
}
83 changes: 72 additions & 11 deletions src/conn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,29 @@ impl AsyncWrite for ConnType {
}
}

/// Existing stream from which a connection can be created.
///
/// A connection may be created from a previously opened TCP or Unix
/// stream (the latter only if Unix domain sockets are supported) by
/// placing an instance of this structure in `LdapConnSettings`.
///
/// Since the stdlib streams can't be cloned, and `LdapConnSettings`
/// derives `Clone`, cloning the enum will produce the `Invalid`
/// variant. Thus, the settings should not be cloned if they
/// contain an existing stream.
pub enum StdStream {
Tcp(std::net::TcpStream),
#[cfg(unix)]
Unix(std::os::unix::net::UnixStream),
Invalid,
}

impl Clone for StdStream {
fn clone(&self) -> StdStream {
StdStream::Invalid
}
}

/// Additional settings for an LDAP connection.
///
/// The structure is opaque for better extensibility. An instance with
Expand All @@ -204,6 +227,7 @@ pub struct LdapConnSettings {
starttls: bool,
#[cfg(any(feature = "tls-native", feature = "tls-rustls"))]
no_tls_verify: bool,
std_stream: Option<StdStream>,
}

impl LdapConnSettings {
Expand Down Expand Up @@ -272,6 +296,21 @@ impl LdapConnSettings {
self.no_tls_verify = no_tls_verify;
self
}

/// Create an LDAP connection using a previously opened standard library
/// stream (TCP or Unix, if applicable.) The full URL must still be provided
/// in order to select connection details, such as TLS establishment or
/// Unix domain socket operation.
///
/// For Unix streams, the URL can be __ldapi:///__, since the path won't
/// be used.
///
/// If the provided stream doesn't match the URL (e.g., a Unix stream is
/// given with the __ldap__ or __ldaps__ URL), an error will be returned.
pub fn set_std_stream(mut self, stream: StdStream) -> Self {
self.std_stream = Some(stream);
self
}
}

enum LoopMode {
Expand Down Expand Up @@ -397,16 +436,27 @@ impl LdapConnAsync {
}

#[cfg(unix)]
async fn new_unix(url: &Url, _settings: LdapConnSettings) -> Result<(Self, Ldap)> {
let path = url.host_str().unwrap_or("");
if path.is_empty() {
return Err(LdapError::EmptyUnixPath);
}
if path.contains(':') {
return Err(LdapError::PortInUnixPath);
}
let dec_path = percent_decode(path.as_bytes()).decode_utf8_lossy();
let stream = UnixStream::connect(dec_path.as_ref()).await?;
async fn new_unix(url: &Url, settings: LdapConnSettings) -> Result<(Self, Ldap)> {
let stream = match settings.std_stream {
None => {
let path = url.host_str().unwrap_or("");
if path.is_empty() {
return Err(LdapError::EmptyUnixPath);
}
if path.contains(':') {
return Err(LdapError::PortInUnixPath);
}
let dec_path = percent_decode(path.as_bytes()).decode_utf8_lossy();
UnixStream::connect(dec_path.as_ref()).await?
}
Some(StdStream::Unix(stream)) => {
stream.set_nonblocking(true)?;
UnixStream::from_std(stream)?
}
Some(StdStream::Tcp(_)) | Some(StdStream::Invalid) => {
return Err(LdapError::MismatchedStreamType)
}
};
Ok(Self::conn_pair(ConnType::Unix(stream)))
}

Expand Down Expand Up @@ -442,7 +492,18 @@ impl LdapConnAsync {
Some(h) if !h.is_empty() => ("localhost", format!("localhost:{}", port)),
_ => panic!("unexpected None from url.host_str()"),
};
let stream = TcpStream::connect(host_port.as_str()).await?;
let stream = match settings.std_stream {
None => TcpStream::connect(host_port.as_str()).await?,
Some(StdStream::Tcp(_)) => {
let stream = match settings.std_stream.take().expect("StdStream") {
StdStream::Tcp(stream) => stream,
_ => panic!("non-tcp stream in enum"),
};
stream.set_nonblocking(true)?;
TcpStream::from_std(stream)?
}
Some(_) => return Err(LdapError::MismatchedStreamType),
};
let (mut conn, mut ldap) = Self::conn_pair(ConnType::Tcp(stream));
match scheme {
"ldap" => (),
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ mod search;
mod sync;
mod util;

pub use conn::{LdapConnAsync, LdapConnSettings};
pub use conn::{LdapConnAsync, LdapConnSettings, StdStream};
pub use filter::parse as parse_filter;
pub use ldap::{Ldap, Mod};
pub use result::{LdapError, LdapResult, SearchResult};
Expand Down
4 changes: 4 additions & 0 deletions src/result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ pub enum LdapError {
#[error("the port must be empty in the ldapi scheme")]
PortInUnixPath,

/// The existing stream in `LdapConnectionSettings` doesn't match the URL.
#[error("the stream type in LdapConnSettings does not match the URL")]
MismatchedStreamType,

/// Encapsulated I/O error.
#[error("I/O error: {source}")]
Io {
Expand Down

0 comments on commit 504d45a

Please sign in to comment.