feat: Add incoming unix domain socket support

This commit is contained in:
selfhoster selfhoster 2026-08-21 11:50:53 +02:00
commit 3331e431dd
5 changed files with 749 additions and 21 deletions

View file

@ -1,9 +1,13 @@
use anyhow::bail;
use serde::Deserialize;
use tokio_listener::{Listener, ListenerAddress, SystemOptions, UserOptions};
use std::net::{Ipv6Addr, SocketAddrV6};
use std::path::Path;
#[derive(Clone, Debug, Deserialize)]
pub struct Config {
pub listen: Option<ListenerAddress>,
pub mapping: Vec<Mapping>,
}
@ -12,6 +16,47 @@ impl Config {
let content = tokio::fs::read(path).await?;
Ok(toml::from_slice(&content)?)
}
pub async fn listener(&self) -> anyhow::Result<Listener> {
let listener_addr = if let Some(listen) = &self.listen {
// For now we only support ADDR:PORT and Unix domain sockets
match listen {
ListenerAddress::Tcp(_) => listen,
ListenerAddress::Path(p) => {
if tokio::fs::try_exists(p).await? {
warn!(
"Socket {} already exists, probably because multildap was not shut down correctly.",
p.display()
);
warn!(
"If another multildap instance is still listening on there, it will no longer receive new connections."
);
}
listen
}
_ => {
error!(
"To use unix domain sockets, use relative or absolute paths, eg. `./ldap.sock` or `/var/run/ldap.sock`"
);
bail!("Invalid listen option, currently parsed to: {:?}", listen);
}
}
} else {
// By default, listen on localhost:389 where a LDAP server usually listens.
// Don't listen on all interfaces by default for security reasons.
&ListenerAddress::Tcp(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 389, 0, 0).into())
};
let system_options: SystemOptions = Default::default();
let mut user_options: UserOptions = Default::default();
// If there's a left over socket, delete it instead of erroring.
// If another daemon is still listening over there, it will no longer
// receive connections.
// TODO: Unix Domain Socket garbage collection (graceful shutdown).
user_options.unix_listen_unlink = true;
Ok(Listener::bind(listener_addr, &system_options, &user_options).await?)
}
}
#[derive(Clone, Debug, Deserialize)]

View file

@ -6,7 +6,7 @@ use futures_util::StreamExt;
use ldap3_proto::LdapCodec;
use ldap3_proto::proto::*;
use tokio::io::{AsyncRead, AsyncWrite, ReadHalf, WriteHalf};
use tokio::net::{TcpListener, TcpStream};
use tokio::net::TcpStream;
use tokio::time::timeout;
use tokio_util::codec::{FramedRead, FramedWrite};
@ -190,8 +190,10 @@ pub async fn client_process<W: AsyncWrite + Unpin, R: AsyncRead + Unpin>(
#[tokio::main(flavor = "current_thread")]
async fn main() {
if let Err(_) = std::env::var("RUST_LOG") {
unsafe { std::env::set_var("RUST_LOG", "info"); }
if std::env::var("RUST_LOG").is_err() {
unsafe {
std::env::set_var("RUST_LOG", "info");
}
}
pretty_env_logger::formatted_timed_builder()
@ -211,12 +213,18 @@ async fn main() {
}
};
let state = Arc::new(config);
// Let the listening port ready.
let listener = TcpListener::bind("127.0.0.1:3389").await.unwrap();
let mut listener = match config.listener().await {
Ok(listener) => listener,
Err(e) => {
error!("Listening on port/socket failed: {e}");
std::process::exit(1);
}
};
info!("Listening on {:?}", listener);
let state = Arc::new(config);
loop {
match listener.accept().await {
Ok((tcpstream, client_socket_addr)) => {
@ -226,7 +234,10 @@ async fn main() {
let w = FramedWrite::new(w, LdapCodec::new(None, None));
tokio::spawn(client_process(r, w, state.clone()));
}
Err(_e) => continue,
Err(e) => {
warn!("{}", e);
continue;
}
}
}
}