feat: Basic HTTP listener

This commit is contained in:
selfhoster selfhoster 2026-09-03 21:50:32 +02:00
commit 452da73413
7 changed files with 350 additions and 7 deletions

View file

@ -3,7 +3,11 @@ use argh::FromArgs;
/// Run the llldap server
#[derive(FromArgs)]
pub struct CliArgs {
/// address or socket to listen on
#[argh(positional, default = "String::from(\"127.0.0.1:3389\")")]
pub listen: String,
/// address or socket to listen on for LDAP connections
#[argh(option, default = "String::from(\"127.0.0.1:3389\")")]
pub listen_ldap: String,
/// address or socket to listen on for HTTP connections
#[argh(option, default = "String::from(\"127.0.0.1:3390\")")]
pub listen_http: String,
}

View file

@ -14,7 +14,7 @@ impl<D: DatabaseInterface> DatabaseInterface for Database<D> {
}
}
pub trait DatabaseInterface {
pub trait DatabaseInterface: Clone + Send + Sync + 'static {
async fn get_user(&self, user: &UserRef) -> Result<Option<User>, BoxedError>;
async fn create_user(
&mut self,

57
src/http/mod.rs Normal file
View file

@ -0,0 +1,57 @@
use axum::Router;
use axum::routing::get;
use axum::serve::Listener as AxumListener;
use crate::db::{Database, DatabaseInterface};
use crate::listener::{Listener, ListenerKind};
use crate::stream::{AbstractSocketAddr, AbstractStreamKind};
impl AxumListener for Listener {
type Io = AbstractStreamKind;
type Addr = AbstractSocketAddr;
async fn accept(&mut self) -> (Self::Io, Self::Addr) {
loop {
let res = match &self.kind {
ListenerKind::Tcp(l) => l
.accept()
.await
.map(|(stream, remote_addr)| (stream.into(), remote_addr.into())),
ListenerKind::Uds(l) => l
.accept()
.await
.map(|(stream, remote_addr)| (stream.into(), remote_addr.into())),
};
match res {
Ok((stream, remote_addr)) => return (stream, remote_addr),
Err(e) => {
// Here the error could be fatal, or could simply be that a client aborted the connected,
// in which case we don't want to crash the server, simply skip this client connection.
// https://doc.rust-lang.org/stable/std/net/struct.TcpListener.html#errors
match e.kind() {
std::io::ErrorKind::ConnectionAborted => {}
_ => panic!("Unrecoverable HTTP client connection error: {e}"),
}
}
}
}
}
fn local_addr(&self) -> std::io::Result<Self::Addr> {
match &self.kind {
ListenerKind::Tcp(l) => l.local_addr().map(Into::into),
ListenerKind::Uds(l) => l.local_addr().map(Into::into),
}
}
}
pub async fn http_listen<D: DatabaseInterface>(listener: Listener, db: Database<D>) {
let app = Router::new().route("/", get(handler)).with_state(db);
axum::serve(listener, app).await.unwrap();
}
pub async fn handler() -> &'static str {
"hello world"
}

View file

@ -153,7 +153,7 @@ fn search_entry_from_user(user: &User, req_attrs: &[String]) -> LdapSearchResult
for attr in req_attrs {
if let Some(attr_value) = match attr.as_str() {
"uid" => Some(user.username.clone()),
"cn"|"mail" => Some(user.mail.clone()),
"cn" | "mail" => Some(user.mail.clone()),
_ => {
tracing::warn!("Ignoring unknown attr in search query: {attr}");
None

View file

@ -3,10 +3,14 @@
mod cli;
mod db;
mod error;
#[cfg(feature = "http")]
mod http;
mod ldap;
mod listener;
mod stream;
#[cfg(feature = "http")]
use crate::http::http_listen;
use cli::CliArgs;
use db::{Database, DatabaseInterface, MemoryDatabase, User};
use error::GlobalError;
@ -39,14 +43,21 @@ async fn main() -> Result<(), GlobalError> {
// .init();
// tracing::subscriber::set_global_default(subscriber)?;
let listener = ListenerPath::new(&cli.listen)?.listener().await?;
let ldap_listener = ListenerPath::new(&cli.listen_ldap)?.listener().await?;
let mut db = MemoryDatabase::new();
create_dummy_users(&mut db).await;
#[cfg(feature = "http")]
{
let http_listener = ListenerPath::new(&cli.listen_http)?.listener().await?;
let http_db = db.clone();
tokio::spawn(http_listen(http_listener, http_db));
}
// 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? {
while let Some(stream) = ldap_listener.accept_ldap().await? {
let db = db.clone();
tokio::spawn(ldap_handler(stream, db));
}