Add basic DAV server

This commit is contained in:
Quentin 2024-02-26 23:59:29 +01:00
parent 0dcf69f180
commit 3d3fd80629
Signed by: quentin
GPG key ID: E9602264D639FF68
6 changed files with 129 additions and 13 deletions

14
Cargo.lock generated
View file

@ -28,7 +28,7 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]] [[package]]
name = "aerogramme" name = "aerogramme"
version = "0.2.2" version = "0.3.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"argon2", "argon2",
@ -46,6 +46,8 @@ dependencies = [
"eml-codec", "eml-codec",
"futures", "futures",
"hex", "hex",
"http-body-util",
"hyper 1.2.0",
"hyper-rustls 0.26.0", "hyper-rustls 0.26.0",
"hyper-util", "hyper-util",
"im", "im",
@ -58,6 +60,7 @@ dependencies = [
"log", "log",
"nix", "nix",
"nom 7.1.3", "nom 7.1.3",
"quick-xml",
"rand", "rand",
"rmp-serde", "rmp-serde",
"rpassword", "rpassword",
@ -2691,6 +2694,15 @@ dependencies = [
"prost", "prost",
] ]
[[package]]
name = "quick-xml"
version = "0.31.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.35" version = "1.0.35"

View file

@ -1,6 +1,6 @@
[package] [package]
name = "aerogramme" name = "aerogramme"
version = "0.2.2" version = "0.3.0"
authors = ["Alex Auvolat <alex@adnab.me>", "Quentin Dufour <quentin@dufour.io>"] authors = ["Alex Auvolat <alex@adnab.me>", "Quentin Dufour <quentin@dufour.io>"]
edition = "2021" edition = "2021"
license = "EUPL-1.2" license = "EUPL-1.2"
@ -18,6 +18,7 @@ backtrace = "0.3"
console-subscriber = "0.2" console-subscriber = "0.2"
tracing-subscriber = "0.3" tracing-subscriber = "0.3"
tracing = "0.1" tracing = "0.1"
thiserror = "1.0.56"
# language extensions # language extensions
lazy_static = "1.4" lazy_static = "1.4"
@ -32,13 +33,27 @@ chrono = { version = "0.4", default-features = false, features = ["alloc"] }
nix = { version = "0.27", features = ["signal"] } nix = { version = "0.27", features = ["signal"] }
clap = { version = "3.1.18", features = ["derive", "env"] } clap = { version = "3.1.18", features = ["derive", "env"] }
# serialization & parsing # email protocols
eml-codec = "0.1.2"
smtp-message = { git = "http://github.com/Alexis211/kannader", branch = "feature/lmtp" }
smtp-server = { git = "http://github.com/Alexis211/kannader", branch = "feature/lmtp" }
imap-codec = { version = "2.0.0", features = ["bounded-static", "ext_condstore_qresync"] }
imap-flow = { git = "https://github.com/duesee/imap-flow.git", branch = "main" }
# http & web
http-body-util = "0.1"
hyper = "1.2"
hyper-rustls = { version = "0.26", features = ["http2"] }
hyper-util = { version = "0.1", features = ["full"] }
# serialization, compression & parsing
serde = "1.0.137" serde = "1.0.137"
rmp-serde = "0.15" rmp-serde = "0.15"
toml = "0.5" toml = "0.5"
base64 = "0.21" base64 = "0.21"
hex = "0.4" hex = "0.4"
nom = "7.1" nom = "7.1"
quick-xml = "0.31"
zstd = { version = "0.9", default-features = false } zstd = { version = "0.9", default-features = false }
# cryptography & security # cryptography & security
@ -48,8 +63,6 @@ rand = "0.8.5"
rustls = "0.22" rustls = "0.22"
rustls-pemfile = "2.0" rustls-pemfile = "2.0"
tokio-rustls = "0.25" tokio-rustls = "0.25"
hyper-rustls = { version = "0.26", features = ["http2"] }
hyper-util = { version = "0.1", features = ["full"] }
rpassword = "7.0" rpassword = "7.0"
# login # login
@ -62,14 +75,6 @@ aws-sdk-s3 = "1"
aws-smithy-runtime = "1" aws-smithy-runtime = "1"
aws-smithy-runtime-api = "1" aws-smithy-runtime-api = "1"
# email protocols
eml-codec = "0.1.2"
smtp-message = { git = "http://github.com/Alexis211/kannader", branch = "feature/lmtp" }
smtp-server = { git = "http://github.com/Alexis211/kannader", branch = "feature/lmtp" }
imap-codec = { version = "2.0.0", features = ["bounded-static", "ext_condstore_qresync"] }
imap-flow = { git = "https://github.com/duesee/imap-flow.git", branch = "main" }
thiserror = "1.0.56"
[dev-dependencies] [dev-dependencies]
[patch.crates-io] [patch.crates-io]

View file

@ -10,6 +10,7 @@ use serde::{Deserialize, Serialize};
pub struct CompanionConfig { pub struct CompanionConfig {
pub pid: Option<PathBuf>, pub pid: Option<PathBuf>,
pub imap: ImapUnsecureConfig, pub imap: ImapUnsecureConfig,
// @FIXME Add DAV
#[serde(flatten)] #[serde(flatten)]
pub users: LoginStaticConfig, pub users: LoginStaticConfig,
@ -22,6 +23,7 @@ pub struct ProviderConfig {
pub imap_unsecure: Option<ImapUnsecureConfig>, pub imap_unsecure: Option<ImapUnsecureConfig>,
pub lmtp: Option<LmtpConfig>, pub lmtp: Option<LmtpConfig>,
pub auth: Option<AuthConfig>, pub auth: Option<AuthConfig>,
pub dav_unsecure: Option<DavUnsecureConfig>,
pub users: UserManagement, pub users: UserManagement,
} }
@ -51,6 +53,11 @@ pub struct ImapConfig {
pub key: PathBuf, pub key: PathBuf,
} }
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct DavUnsecureConfig {
pub bind_addr: SocketAddr,
}
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ImapUnsecureConfig { pub struct ImapUnsecureConfig {
pub bind_addr: SocketAddr, pub bind_addr: SocketAddr,

75
src/dav/mod.rs Normal file
View file

@ -0,0 +1,75 @@
use std::net::SocketAddr;
use anyhow::Result;
use hyper::service::service_fn;
use hyper::{Request, Response, body::Bytes};
use hyper::server::conn::http1 as http;
use hyper_util::rt::TokioIo;
use http_body_util::Full;
use futures::stream::{FuturesUnordered, StreamExt};
use tokio::net::TcpListener;
use tokio::sync::watch;
use crate::config::DavUnsecureConfig;
use crate::login::ArcLoginProvider;
pub struct Server {
bind_addr: SocketAddr,
login_provider: ArcLoginProvider,
}
pub fn new_unsecure(config: DavUnsecureConfig, login: ArcLoginProvider) -> Server {
Server {
bind_addr: config.bind_addr,
login_provider: login,
}
}
impl Server {
pub async fn run(self: Self, mut must_exit: watch::Receiver<bool>) -> Result<()> {
let tcp = TcpListener::bind(self.bind_addr).await?;
tracing::info!("DAV server listening on {:#}", self.bind_addr);
let mut connections = FuturesUnordered::new();
while !*must_exit.borrow() {
let wait_conn_finished = async {
if connections.is_empty() {
futures::future::pending().await
} else {
connections.next().await
}
};
let (socket, remote_addr) = tokio::select! {
a = tcp.accept() => a?,
_ = wait_conn_finished => continue,
_ = must_exit.changed() => continue,
};
tracing::info!("DAV: accepted connection from {}", remote_addr);
let stream = TokioIo::new(socket);
let conn = tokio::spawn(async {
//@FIXME should create a generic "public web" server on which "routers" could be
//abitrarily bound
//@FIXME replace with a handler supporting http2 and TLS
match http::Builder::new().serve_connection(stream, service_fn(router)).await {
Err(e) => tracing::warn!(err=?e, "connection failed"),
Ok(()) => tracing::trace!("connection terminated with success"),
}
});
connections.push(conn);
}
drop(tcp);
tracing::info!("DAV server shutting down, draining remaining connections...");
while connections.next().await.is_some() {}
Ok(())
}
}
async fn router(req: Request<impl hyper::body::Body>) -> Result<Response<Full<Bytes>>> {
let url_exploded: Vec<_> = req.uri().path().split(",").collect();
match url_exploded {
_ => unimplemented!(),
}
Ok(Response::new(Full::new(Bytes::from("Hello World!"))))
}

View file

@ -4,6 +4,7 @@ mod auth;
mod bayou; mod bayou;
mod config; mod config;
mod cryptoblob; mod cryptoblob;
mod dav;
mod imap; mod imap;
mod k2v_util; mod k2v_util;
mod lmtp; mod lmtp;
@ -187,6 +188,9 @@ async fn main() -> Result<()> {
imap_unsecure: Some(ImapUnsecureConfig { imap_unsecure: Some(ImapUnsecureConfig {
bind_addr: SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), 1143), bind_addr: SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), 1143),
}), }),
dav_unsecure: Some(DavUnsecureConfig {
bind_addr: SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), 8087),
}),
lmtp: Some(LmtpConfig { lmtp: Some(LmtpConfig {
bind_addr: SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), 1025), bind_addr: SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), 1025),
hostname: "example.tld".to_string(), hostname: "example.tld".to_string(),

View file

@ -9,6 +9,7 @@ use tokio::sync::watch;
use crate::auth; use crate::auth;
use crate::config::*; use crate::config::*;
use crate::dav;
use crate::imap; use crate::imap;
use crate::lmtp::*; use crate::lmtp::*;
use crate::login::ArcLoginProvider; use crate::login::ArcLoginProvider;
@ -19,6 +20,7 @@ pub struct Server {
imap_unsecure_server: Option<imap::Server>, imap_unsecure_server: Option<imap::Server>,
imap_server: Option<imap::Server>, imap_server: Option<imap::Server>,
auth_server: Option<auth::AuthServer>, auth_server: Option<auth::AuthServer>,
dav_unsecure_server: Option<dav::Server>,
pid_file: Option<PathBuf>, pid_file: Option<PathBuf>,
} }
@ -34,6 +36,7 @@ impl Server {
imap_unsecure_server, imap_unsecure_server,
imap_server: None, imap_server: None,
auth_server: None, auth_server: None,
dav_unsecure_server: None,
pid_file: config.pid, pid_file: config.pid,
}) })
} }
@ -57,11 +60,15 @@ impl Server {
let auth_server = config let auth_server = config
.auth .auth
.map(|auth| auth::AuthServer::new(auth, login.clone())); .map(|auth| auth::AuthServer::new(auth, login.clone()));
let dav_unsecure_server = config
.dav_unsecure
.map(|dav_config| dav::new_unsecure(dav_config, login.clone()));
Ok(Self { Ok(Self {
lmtp_server, lmtp_server,
imap_unsecure_server, imap_unsecure_server,
imap_server, imap_server,
dav_unsecure_server,
auth_server, auth_server,
pid_file: config.pid, pid_file: config.pid,
}) })
@ -112,6 +119,12 @@ impl Server {
None => Ok(()), None => Ok(()),
Some(a) => a.run(exit_signal.clone()).await, Some(a) => a.run(exit_signal.clone()).await,
} }
},
async {
match self.dav_unsecure_server {
None => Ok(()),
Some(s) => s.run(exit_signal.clone()).await,
}
} }
)?; )?;