diff --git a/src/db/common.rs b/src/db/common.rs new file mode 100644 index 0000000..fd6d16a --- /dev/null +++ b/src/db/common.rs @@ -0,0 +1,36 @@ +use tokio::sync::RwLock; + +use std::sync::Arc; + +use crate::db::{DatabaseInterface, UserRef}; + +#[derive(Clone, Debug)] +pub struct Database { + pub inner: Arc>, +} + +impl Database { + pub fn new(db: D) -> Self { + Self { + inner: Arc::new(RwLock::new(db)), + } + } +} + +impl Database { + /// Return false if the user doesn't exist, or the password is wrong. + /// + /// TODO: should we return something else when the account doesn't exist? + /// or is it a feature to behave in the same way? + /// + /// TODO: we may want to make sure the method runs in constant time + /// to avoid leaking information about existing users... + /// or maybe we do not care. + async fn check_password(&self, user: &UserRef, password: &str) -> bool { + let Some(user) = self.get_user(user).await else { + return false; + }; + + user.password == password + } +} diff --git a/src/db/error.rs b/src/db/error.rs new file mode 100644 index 0000000..e45a796 --- /dev/null +++ b/src/db/error.rs @@ -0,0 +1,14 @@ +use std::fmt; + +use crate::db::UserRef; + +#[derive(Debug)] +pub struct UserAlreadyExists(pub UserRef); + +impl fmt::Display for UserAlreadyExists { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "User already exists: {}", self.0) + } +} + +impl std::error::Error for UserAlreadyExists {} diff --git a/src/db/group.rs b/src/db/group.rs new file mode 100644 index 0000000..428d8f9 --- /dev/null +++ b/src/db/group.rs @@ -0,0 +1,5 @@ +#[derive(Clone, Debug)] +pub struct Group { + name: String, + domain: String, +} diff --git a/src/db/interface.rs b/src/db/interface.rs new file mode 100644 index 0000000..ef4517b --- /dev/null +++ b/src/db/interface.rs @@ -0,0 +1,17 @@ +use crate::db::error::*; +use crate::db::{Database, User, UserRef}; + +impl DatabaseInterface for Database { + async fn get_user(&self, user: &UserRef) -> Option { + self.inner.read().await.get_user(user).await + } + + async fn create_user(&mut self, user: User) -> Result<(), UserAlreadyExists> { + self.inner.write().await.create_user(user).await + } +} + +pub trait DatabaseInterface { + async fn get_user(&self, user: &UserRef) -> Option; + async fn create_user(&mut self, user: User) -> Result<(), UserAlreadyExists>; +} diff --git a/src/db/memory.rs b/src/db/memory.rs new file mode 100644 index 0000000..f151a8e --- /dev/null +++ b/src/db/memory.rs @@ -0,0 +1,38 @@ +use std::future::{Future, ready}; + +use crate::db::error::*; +use crate::db::{Database, DatabaseInterface, Group, User, UserRef}; + +#[derive(Clone, Debug, Default)] +pub struct MemoryDatabase { + pub users: Vec, + pub groups: Vec, +} + +impl MemoryDatabase { + pub fn new() -> Database { + Database::new(Self::default()) + } +} + +impl DatabaseInterface for MemoryDatabase { + fn get_user(&self, req_user: &UserRef) -> impl Future> { + for user in &self.users { + if user.username == req_user.username && user.domain == req_user.domain { + return ready(Some(user.clone())); + } + } + + ready(None) + } + + async fn create_user(&mut self, user: User) -> Result<(), UserAlreadyExists> { + let user_ref = user.user_ref(); + if self.get_user(&user_ref).await.is_some() { + return Err(UserAlreadyExists(user_ref)); + } + + self.users.push(user); + Ok(()) + } +} diff --git a/src/db/mod.rs b/src/db/mod.rs new file mode 100644 index 0000000..b54747a --- /dev/null +++ b/src/db/mod.rs @@ -0,0 +1,11 @@ +mod common; +pub use common::Database; +pub mod error; +mod group; +pub use group::Group; +mod interface; +pub use interface::DatabaseInterface; +mod memory; +pub use memory::MemoryDatabase; +mod user; +pub use user::{User, UserRef}; diff --git a/src/db/user.rs b/src/db/user.rs new file mode 100644 index 0000000..4301d73 --- /dev/null +++ b/src/db/user.rs @@ -0,0 +1,37 @@ +use std::fmt; + +#[derive(Clone, Debug)] +pub struct UserRef { + pub username: String, + pub domain: String, +} + +impl fmt::Display for UserRef { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}@{}", self.username, self.domain) + } +} + +#[derive(Clone, Debug)] +pub struct User { + pub username: String, + pub domain: String, + pub password: String, + pub mail: String, + // recovery_mail: String, +} + +impl User { + pub fn user_ref(&self) -> UserRef { + UserRef { + username: self.username.clone(), + domain: self.domain.clone(), + } + } +} + +impl fmt::Display for User { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}@{}", self.username, self.domain) + } +} diff --git a/src/ldap/handler.rs b/src/ldap/handler.rs index f13a992..d8bd7cc 100644 --- a/src/ldap/handler.rs +++ b/src/ldap/handler.rs @@ -1,9 +1,10 @@ use ldap3_proto::LdapMsg; +use crate::db::{Database, DatabaseInterface}; use crate::ldap::{LdapStream, LdapStreamError}; -#[tracing::instrument(name = "ldap", skip(s), fields(session = %s.session))] -pub async fn ldap_handler(mut s: LdapStream) { +#[tracing::instrument(name = "ldap", skip(s, db), fields(session = %s.session))] +pub async fn ldap_handler(mut s: LdapStream, db: Database) { tracing::info! { remote_addr = ?s.remote_addr, "New client connection" diff --git a/src/main.rs b/src/main.rs index 2c5430e..95eacc4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,16 +1,31 @@ // #![deny(warnings)] mod cli; +mod db; mod error; mod ldap; mod listener; mod stream; use cli::CliArgs; +use db::{Database, DatabaseInterface, MemoryDatabase, User}; use error::GlobalError; use ldap::ldap_handler; use listener::ListenerPath; +async fn create_dummy_users(db: &mut Database) { + for domain in &["a", "b", "c"] { + db.create_user(User { + username: domain.to_string(), + domain: format!("{domain}.localhost"), + password: "adminadmin".to_string(), + mail: format!("{domain}@{domain}.localhost"), + }) + .await + .unwrap(); + } +} + #[tokio::main(flavor = "current_thread")] async fn main() -> Result<(), GlobalError> { let cli: CliArgs = argh::from_env(); @@ -25,10 +40,14 @@ async fn main() -> Result<(), GlobalError> { let listener = ListenerPath::new(&cli.listen)?.listener().await?; + let mut db = MemoryDatabase::new(); + create_dummy_users(&mut db).await; + // If the connection is None, it's because the client aborted early // so there's nothing to do about it. while let Some(stream) = listener.accept_ldap().await? { - tokio::spawn(ldap_handler(stream)); + let db = db.clone(); + tokio::spawn(ldap_handler(stream, db)); } Ok(())