diff --git a/Cargo.toml b/Cargo.toml index b256241..7b71d5d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,21 +4,24 @@ edition = "2018" name = "lldap" version = "0.1.0" [dependencies] -actix = "*" -actix-rt = "1.1.1" -actix-server = "*" -actix-service = "*" -actix-web = "*" +actix = "0.11.0-beta.3" +actix-rt = "*" +actix-server = "2.0.0-beta.3" +actix-service = "2.0.0-beta.4" +actix-web = "4.0.0-beta.3" anyhow = "*" clap = "3.0.0-beta.2" +futures = "*" futures-util = "*" http = "*" +ldap3_server = "*" log = "*" serde = "*" thiserror = "*" -tokio = "0.2.25" +tokio = { version = "*", features = ["full"] } +tokio-util = "*" tracing = "*" -tracing-actix-web = "*" +tracing-actix-web = "0.3.0-beta.2" tracing-log = "*" tracing-subscriber = "*" diff --git a/examples/ldap_server.rs b/examples/ldap_server.rs new file mode 100644 index 0000000..4b4f9c5 --- /dev/null +++ b/examples/ldap_server.rs @@ -0,0 +1,150 @@ +use tokio::net::{TcpListener, TcpStream}; +// use tokio::stream::StreamExt; +use futures::SinkExt; +use futures::StreamExt; +use std::convert::TryFrom; +use std::net; +use std::str::FromStr; +use tokio_util::codec::{FramedRead, FramedWrite}; + +use ldap3_server::simple::*; +use ldap3_server::LdapCodec; + +pub struct LdapSession { + dn: String, +} + +impl LdapSession { + pub fn do_bind(&mut self, sbr: &SimpleBindRequest) -> LdapMsg { + if sbr.dn == "cn=Directory Manager" && sbr.pw == "password" { + self.dn = sbr.dn.to_string(); + sbr.gen_success() + } else if sbr.dn == "" && sbr.pw == "" { + self.dn = "Anonymous".to_string(); + sbr.gen_success() + } else { + sbr.gen_invalid_cred() + } + } + + pub fn do_search(&mut self, lsr: &SearchRequest) -> Vec { + vec![ + lsr.gen_result_entry(LdapSearchResultEntry { + dn: "cn=hello,dc=example,dc=com".to_string(), + attributes: vec![ + LdapPartialAttribute { + atype: "objectClass".to_string(), + vals: vec!["cursed".to_string()], + }, + LdapPartialAttribute { + atype: "cn".to_string(), + vals: vec!["hello".to_string()], + }, + ], + }), + lsr.gen_result_entry(LdapSearchResultEntry { + dn: "cn=world,dc=example,dc=com".to_string(), + attributes: vec![ + LdapPartialAttribute { + atype: "objectClass".to_string(), + vals: vec!["cursed".to_string()], + }, + LdapPartialAttribute { + atype: "cn".to_string(), + vals: vec!["world".to_string()], + }, + ], + }), + lsr.gen_success(), + ] + } + + pub fn do_whoami(&mut self, wr: &WhoamiRequest) -> LdapMsg { + wr.gen_success(format!("dn: {}", self.dn).as_str()) + } +} + +async fn handle_client(socket: TcpStream, _paddr: net::SocketAddr) { + // Configure the codec etc. + let (r, w) = tokio::io::split(socket); + let mut reqs = FramedRead::new(r, LdapCodec); + let mut resp = FramedWrite::new(w, LdapCodec); + + let mut session = LdapSession { + dn: "Anonymous".to_string(), + }; + + while let Some(msg) = reqs.next().await { + let server_op = match msg + .map_err(|_e| ()) + .and_then(|msg| ServerOps::try_from(msg)) + { + Ok(v) => v, + Err(_) => { + let _err = resp + .send(DisconnectionNotice::gen( + LdapResultCode::Other, + "Internal Server Error", + )) + .await; + let _err = resp.flush().await; + return; + } + }; + + let result = match server_op { + ServerOps::SimpleBind(sbr) => vec![session.do_bind(&sbr)], + ServerOps::Search(sr) => session.do_search(&sr), + ServerOps::Unbind(_) => { + // No need to notify on unbind (per rfc4511) + return; + } + ServerOps::Whoami(wr) => vec![session.do_whoami(&wr)], + }; + + for rmsg in result.into_iter() { + if let Err(_) = resp.send(rmsg).await { + return; + } + } + + if let Err(_) = resp.flush().await { + return; + } + } + // Client disconnected +} + +async fn acceptor(listener: Box) { + println!("start function"); + loop { + println!("loop"); + match listener.accept().await { + Ok((socket, paddr)) => { + println!("OK listener"); + tokio::spawn(handle_client(socket, paddr)); + } + Err(_e) => { + println!("Err listener"); + //pass + } + } + } +} + +#[tokio::main] +async fn main() -> () { + let addr = net::SocketAddr::from_str("0.0.0.0:12345").unwrap(); + let listener = Box::new(TcpListener::bind(&addr).await.unwrap()); + + // Initiate the acceptor task. + let _handle = tokio::spawn(async move { + println!("inside tokio"); + acceptor(listener).await + }) + .await + .expect("tokio should start"); + + println!("started ldap://127.0.0.1:12345 ..."); + tokio::signal::ctrl_c().await.unwrap(); +} diff --git a/src/infra/ldap_server.rs b/src/infra/ldap_server.rs new file mode 100644 index 0000000..7d55b66 --- /dev/null +++ b/src/infra/ldap_server.rs @@ -0,0 +1,124 @@ +use crate::infra::configuration::Configuration; +use actix_rt::net::TcpStream; +use actix_server::Server; +use actix_service::pipeline_factory; +use anyhow::Result; +use futures_util::future::{err, ok}; +use log::*; + +use ldap3_server::simple::*; +use ldap3_server::LdapCodec; + +pub struct LdapSession { + dn: String, +} + +pub fn init(config: Configuration) -> Result<()> { + debug!("LDAP: init"); + actix::run(run_ldap_server(config))?; + + Ok(()) +} + +impl LdapSession { + pub fn do_bind(&mut self, sbr: &SimpleBindRequest) -> LdapMsg { + if sbr.dn == "cn=Directory Manager" && sbr.pw == "password" { + self.dn = sbr.dn.to_string(); + sbr.gen_success() + } else if sbr.dn == "" && sbr.pw == "" { + self.dn = "Anonymous".to_string(); + sbr.gen_success() + } else { + sbr.gen_invalid_cred() + } + } + + pub fn do_search(&mut self, lsr: &SearchRequest) -> Vec { + vec![ + lsr.gen_result_entry(LdapSearchResultEntry { + dn: "cn=hello,dc=example,dc=com".to_string(), + attributes: vec![ + LdapPartialAttribute { + atype: "objectClass".to_string(), + vals: vec!["cursed".to_string()], + }, + LdapPartialAttribute { + atype: "cn".to_string(), + vals: vec!["hello".to_string()], + }, + ], + }), + lsr.gen_result_entry(LdapSearchResultEntry { + dn: "cn=world,dc=example,dc=com".to_string(), + attributes: vec![ + LdapPartialAttribute { + atype: "objectClass".to_string(), + vals: vec!["cursed".to_string()], + }, + LdapPartialAttribute { + atype: "cn".to_string(), + vals: vec!["world".to_string()], + }, + ], + }), + lsr.gen_success(), + ] + } + + pub fn do_whoami(&mut self, wr: &WhoamiRequest) -> LdapMsg { + wr.gen_success(format!("dn: {}", self.dn).as_str()) + } +} + +async fn run_ldap_server(config: Configuration) { + use futures_util::SinkExt; + use futures_util::StreamExt; + use std::convert::TryFrom; + use tokio_util::codec::{FramedRead, FramedWrite}; + + Server::build() + .bind("test-tcp", ("0.0.0.0", config.ldap_port), move || { + pipeline_factory(move |mut stream: TcpStream| async { + // Configure the codec etc. + let (r, w) = stream.split(); + let mut reqs = FramedRead::new(r, LdapCodec); + let mut resp = FramedWrite::new(w, LdapCodec); + + let mut session = LdapSession { + dn: "Anonymous".to_string(), + }; + + while let Some(msg) = reqs.next().await { + let server_op = match msg + .map_err(|_e| ()) + .and_then(|msg| ServerOps::try_from(msg)) + { + Ok(aValue) => aValue, + Err(anError) => { + let _err = resp + .send(DisconnectionNotice::gen( + LdapResultCode::Other, + "Internal Server Error", + )) + .await; + let _err = resp.flush().await; + break; + } + }; + } + + ok::(stream) + }) + .map_err(|err| error!("Service Error: {:?}", err)) + // catch + .and_then(move |_| { + // finally + ok(()) + }) + }) + .unwrap() + .workers(1) + .run() + .await + .unwrap(); +} diff --git a/src/infra/mod.rs b/src/infra/mod.rs index 22b66f3..69ca5ca 100644 --- a/src/infra/mod.rs +++ b/src/infra/mod.rs @@ -1,4 +1,5 @@ pub mod cli; pub mod configuration; +pub mod ldap_server; pub mod logging; pub mod tcp_server; diff --git a/src/main.rs b/src/main.rs index 171ff2e..24593a1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,7 +13,8 @@ fn main() -> Result<()> { debug!("CLI: {:#?}", cli_opts); debug!("Configuration: {:#?}", config); - infra::tcp_server::init(config)?; + // infra::tcp_server::init(config)?; + infra::ldap_server::init(config)?; info!("End."); Ok(())