aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorQuentin Dufour <quentin@deuxfleurs.fr>2024-02-26 23:59:29 +0100
committerQuentin Dufour <quentin@deuxfleurs.fr>2024-02-26 23:59:29 +0100
commit3d3fd80629c804b0593692318a94dc9f344936fd (patch)
tree33514ab96c0237ad40227e276a29750d2a05a92b
parent0dcf69f180f5a7b71b6ad2ac67e4cdd81e5154f1 (diff)
downloadaerogramme-3d3fd80629c804b0593692318a94dc9f344936fd.tar.gz
aerogramme-3d3fd80629c804b0593692318a94dc9f344936fd.zip
Add basic DAV server
-rw-r--r--Cargo.lock14
-rw-r--r--Cargo.toml29
-rw-r--r--src/config.rs7
-rw-r--r--src/dav/mod.rs75
-rw-r--r--src/main.rs4
-rw-r--r--src/server.rs13
6 files changed, 129 insertions, 13 deletions
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",
@@ -2692,6 +2695,15 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
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 <alex@adnab.me>", "Quentin Dufour <quentin@dufour.io>"]
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<PathBuf>,
pub imap: ImapUnsecureConfig,
+ // @FIXME Add DAV
#[serde(flatten)]
pub users: LoginStaticConfig,
@@ -22,6 +23,7 @@ pub struct ProviderConfig {
pub imap_unsecure: Option<ImapUnsecureConfig>,
pub lmtp: Option<LmtpConfig>,
pub auth: Option<AuthConfig>,
+ pub dav_unsecure: Option<DavUnsecureConfig>,
pub users: UserManagement,
}
@@ -52,6 +54,11 @@ pub struct ImapConfig {
}
#[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<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!"))))
+}
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>,
imap_server: Option<imap::Server>,
auth_server: Option<auth::AuthServer>,
+ dav_unsecure_server: Option<dav::Server>,
pid_file: Option<PathBuf>,
}
@@ -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,
+ }
}
)?;