From 3d3fd80629c804b0593692318a94dc9f344936fd Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Mon, 26 Feb 2024 23:59:29 +0100 Subject: [PATCH] Add basic DAV server --- Cargo.lock | 14 +++++++++- Cargo.toml | 29 +++++++++++-------- src/config.rs | 7 +++++ src/dav/mod.rs | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 4 +++ src/server.rs | 13 +++++++++ 6 files changed, 129 insertions(+), 13 deletions(-) create mode 100644 src/dav/mod.rs diff --git a/Cargo.lock b/Cargo.lock index a50d101..c918f48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,7 +28,7 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aerogramme" -version = "0.2.2" +version = "0.3.0" dependencies = [ "anyhow", "argon2", @@ -46,6 +46,8 @@ dependencies = [ "eml-codec", "futures", "hex", + "http-body-util", + "hyper 1.2.0", "hyper-rustls 0.26.0", "hyper-util", "im", @@ -58,6 +60,7 @@ dependencies = [ "log", "nix", "nom 7.1.3", + "quick-xml", "rand", "rmp-serde", "rpassword", @@ -2691,6 +2694,15 @@ dependencies = [ "prost", ] +[[package]] +name = "quick-xml" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" +dependencies = [ + "memchr", +] + [[package]] name = "quote" version = "1.0.35" diff --git a/Cargo.toml b/Cargo.toml index 7d2e032..218a0ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aerogramme" -version = "0.2.2" +version = "0.3.0" authors = ["Alex Auvolat ", "Quentin Dufour "] edition = "2021" license = "EUPL-1.2" @@ -18,6 +18,7 @@ backtrace = "0.3" console-subscriber = "0.2" tracing-subscriber = "0.3" tracing = "0.1" +thiserror = "1.0.56" # language extensions lazy_static = "1.4" @@ -32,13 +33,27 @@ chrono = { version = "0.4", default-features = false, features = ["alloc"] } nix = { version = "0.27", features = ["signal"] } 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" rmp-serde = "0.15" toml = "0.5" base64 = "0.21" hex = "0.4" nom = "7.1" +quick-xml = "0.31" zstd = { version = "0.9", default-features = false } # cryptography & security @@ -48,8 +63,6 @@ rand = "0.8.5" rustls = "0.22" rustls-pemfile = "2.0" tokio-rustls = "0.25" -hyper-rustls = { version = "0.26", features = ["http2"] } -hyper-util = { version = "0.1", features = ["full"] } rpassword = "7.0" # login @@ -62,14 +75,6 @@ aws-sdk-s3 = "1" aws-smithy-runtime = "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] [patch.crates-io] diff --git a/src/config.rs b/src/config.rs index faaa1ba..7de2eac 100644 --- a/src/config.rs +++ b/src/config.rs @@ -10,6 +10,7 @@ use serde::{Deserialize, Serialize}; pub struct CompanionConfig { pub pid: Option, pub imap: ImapUnsecureConfig, + // @FIXME Add DAV #[serde(flatten)] pub users: LoginStaticConfig, @@ -22,6 +23,7 @@ pub struct ProviderConfig { pub imap_unsecure: Option, pub lmtp: Option, pub auth: Option, + pub dav_unsecure: Option, pub users: UserManagement, } @@ -51,6 +53,11 @@ pub struct ImapConfig { pub key: PathBuf, } +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct DavUnsecureConfig { + pub bind_addr: SocketAddr, +} + #[derive(Serialize, Deserialize, Debug, Clone)] pub struct ImapUnsecureConfig { pub bind_addr: SocketAddr, diff --git a/src/dav/mod.rs b/src/dav/mod.rs new file mode 100644 index 0000000..36d154a --- /dev/null +++ b/src/dav/mod.rs @@ -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) -> 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) -> Result>> { + let url_exploded: Vec<_> = req.uri().path().split(",").collect(); + match url_exploded { + _ => unimplemented!(), + } + Ok(Response::new(Full::new(Bytes::from("Hello World!")))) +} diff --git a/src/main.rs b/src/main.rs index 12a5895..6e3057a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ mod auth; mod bayou; mod config; mod cryptoblob; +mod dav; mod imap; mod k2v_util; mod lmtp; @@ -187,6 +188,9 @@ async fn main() -> Result<()> { imap_unsecure: Some(ImapUnsecureConfig { 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 { bind_addr: SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), 1025), hostname: "example.tld".to_string(), diff --git a/src/server.rs b/src/server.rs index 9899981..09e91ad 100644 --- a/src/server.rs +++ b/src/server.rs @@ -9,6 +9,7 @@ use tokio::sync::watch; use crate::auth; use crate::config::*; +use crate::dav; use crate::imap; use crate::lmtp::*; use crate::login::ArcLoginProvider; @@ -19,6 +20,7 @@ pub struct Server { imap_unsecure_server: Option, imap_server: Option, auth_server: Option, + dav_unsecure_server: Option, pid_file: Option, } @@ -34,6 +36,7 @@ impl Server { imap_unsecure_server, imap_server: None, auth_server: None, + dav_unsecure_server: None, pid_file: config.pid, }) } @@ -57,11 +60,15 @@ impl Server { let auth_server = config .auth .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 { lmtp_server, imap_unsecure_server, imap_server, + dav_unsecure_server, auth_server, pid_file: config.pid, }) @@ -112,6 +119,12 @@ impl Server { None => Ok(()), Some(a) => a.run(exit_signal.clone()).await, } + }, + async { + match self.dav_unsecure_server { + None => Ok(()), + Some(s) => s.run(exit_signal.clone()).await, + } } )?;