diff options
author | Quentin Dufour <quentin@deuxfleurs.fr> | 2024-03-13 15:45:36 +0100 |
---|---|---|
committer | Quentin Dufour <quentin@deuxfleurs.fr> | 2024-03-13 15:45:36 +0100 |
commit | 3abdafb0dbbc9290329e4974e821933426b32f91 (patch) | |
tree | f09e14393c5e1b82ba5788a8ce09b2270b72668d /aero-proto | |
parent | 98adb1e20d90a1538b474659a96450d4c7b264c5 (diff) | |
download | aerogramme-3abdafb0dbbc9290329e4974e821933426b32f91.tar.gz aerogramme-3abdafb0dbbc9290329e4974e821933426b32f91.zip |
TLS + Fix auth
Diffstat (limited to 'aero-proto')
-rw-r--r-- | aero-proto/src/dav.rs | 90 | ||||
-rw-r--r-- | aero-proto/src/imap/mod.rs | 81 |
2 files changed, 78 insertions, 93 deletions
diff --git a/aero-proto/src/dav.rs b/aero-proto/src/dav.rs index 2852d34..42fde2c 100644 --- a/aero-proto/src/dav.rs +++ b/aero-proto/src/dav.rs @@ -1,4 +1,5 @@ use std::net::SocketAddr; +use std::sync::Arc; use anyhow::{anyhow, Result}; use base64::Engine; @@ -10,23 +11,55 @@ use http_body_util::Full; use futures::stream::{FuturesUnordered, StreamExt}; use tokio::net::TcpListener; use tokio::sync::watch; +use tokio_rustls::TlsAcceptor; +use tokio::net::TcpStream; +use hyper::rt::{Read, Write}; +use tokio::io::{AsyncRead, AsyncWrite}; +use rustls_pemfile::{certs, private_key}; -use aero_user::config::DavUnsecureConfig; +use aero_user::config::{DavConfig, DavUnsecureConfig}; use aero_user::login::ArcLoginProvider; use aero_collections::user::User; pub struct Server { bind_addr: SocketAddr, login_provider: ArcLoginProvider, + tls: Option<TlsAcceptor>, } pub fn new_unsecure(config: DavUnsecureConfig, login: ArcLoginProvider) -> Server { Server { bind_addr: config.bind_addr, login_provider: login, + tls: None, } } +pub fn new(config: DavConfig, login: ArcLoginProvider) -> Result<Server> { + let loaded_certs = certs(&mut std::io::BufReader::new(std::fs::File::open( + config.certs, + )?)) + .collect::<Result<Vec<_>, _>>()?; + let loaded_key = private_key(&mut std::io::BufReader::new(std::fs::File::open( + config.key, + )?))? + .unwrap(); + + let tls_config = rustls::ServerConfig::builder() + .with_no_client_auth() + .with_single_cert(loaded_certs, loaded_key)?; + let acceptor = TlsAcceptor::from(Arc::new(tls_config)); + + Ok(Server { + bind_addr: config.bind_addr, + login_provider: login, + tls: Some(acceptor), + }) +} + +trait Stream: Read + Write + Send + Unpin {} +impl<T: Unpin + AsyncRead + AsyncWrite + Send> Stream for TokioIo<T> {} + impl Server { pub async fn run(self: Self, mut must_exit: watch::Receiver<bool>) -> Result<()> { let tcp = TcpListener::bind(self.bind_addr).await?; @@ -47,14 +80,24 @@ impl Server { _ = must_exit.changed() => continue, }; tracing::info!("Accepted connection from {}", remote_addr); - let stream = TokioIo::new(socket); + let stream = match self.build_stream(socket).await { + Ok(v) => v, + Err(e) => { + tracing::error!(err=?e, "TLS acceptor failed"); + continue + } + }; + let login = self.login_provider.clone(); let conn = tokio::spawn(async move { //@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(|req: Request<hyper::body::Incoming>| { let login = login.clone(); + tracing::info!("{:?} {:?}", req.method(), req.uri()); async move { auth(login, req).await } @@ -72,6 +115,16 @@ impl Server { Ok(()) } + + async fn build_stream(&self, socket: TcpStream) -> Result<Box<dyn Stream>> { + match self.tls.clone() { + Some(acceptor) => { + let stream = acceptor.accept(socket).await?; + Ok(Box::new(TokioIo::new(stream))) + } + None => Ok(Box::new(TokioIo::new(socket))), + } + } } //@FIXME We should not support only BasicAuth @@ -80,18 +133,26 @@ async fn auth( req: Request<impl hyper::body::Body>, ) -> Result<Response<Full<Bytes>>> { - let auth_val = match req.headers().get("Authorization") { + tracing::info!("headers: {:?}", req.headers()); + let auth_val = match req.headers().get(hyper::header::AUTHORIZATION) { Some(hv) => hv.to_str()?, - None => return Ok(Response::builder() - .status(401) - .body(Full::new(Bytes::from("Missing Authorization field")))?), + None => { + tracing::info!("Missing authorization field"); + return Ok(Response::builder() + .status(401) + .header("WWW-Authenticate", "Basic realm=\"Aerogramme\"") + .body(Full::new(Bytes::from("Missing Authorization field")))?) + }, }; let b64_creds_maybe_padded = match auth_val.split_once(" ") { Some(("Basic", b64)) => b64, - _ => return Ok(Response::builder() - .status(400) - .body(Full::new(Bytes::from("Unsupported Authorization field")))?), + _ => { + tracing::info!("Unsupported authorization field"); + return Ok(Response::builder() + .status(400) + .body(Full::new(Bytes::from("Unsupported Authorization field")))?) + }, }; // base64urlencoded may have trailing equals, base64urlsafe has not @@ -110,9 +171,13 @@ async fn auth( // Call login provider let creds = match login.login(username, password).await { Ok(c) => c, - Err(_) => return Ok(Response::builder() - .status(401) - .body(Full::new(Bytes::from("Wrong credentials")))?), + Err(_) => { + tracing::info!(user=username, "Wrong credentials"); + return Ok(Response::builder() + .status(401) + .header("WWW-Authenticate", "Basic realm=\"Aerogramme\"") + .body(Full::new(Bytes::from("Wrong credentials")))?) + }, }; // Build a user @@ -124,6 +189,7 @@ async fn auth( async fn router(user: std::sync::Arc<User>, req: Request<impl hyper::body::Body>) -> Result<Response<Full<Bytes>>> { let path_segments: Vec<_> = req.uri().path().split("/").filter(|s| *s != "").collect(); + tracing::info!("router"); match path_segments.as_slice() { [] => tracing::info!("root"), [ username, ..] if *username != user.username => return Ok(Response::builder() diff --git a/aero-proto/src/imap/mod.rs b/aero-proto/src/imap/mod.rs index ae3b58f..7183a78 100644 --- a/aero-proto/src/imap/mod.rs +++ b/aero-proto/src/imap/mod.rs @@ -333,85 +333,4 @@ impl NetLoop { }; } } - - /* - async fn idle_mode(&mut self, mut buff: BytesMut, stop: Arc<Notify>) -> Result<LoopMode> { - // Flush send - loop { - tracing::trace!("flush server send"); - match self.server.progress_send().await? { - Some(..) => continue, - None => break, - } - } - - tokio::select! { - // Receiving IDLE event from background - maybe_msg = self.resp_rx.recv() => match maybe_msg { - // Session decided idle is terminated - Some(ResponseOrIdle::Response(response)) => { - tracing::trace!("server imap session said idle is done, sending response done, switching to interactive"); - for body_elem in response.body.into_iter() { - let _handle = match body_elem { - Body::Data(d) => self.server.enqueue_data(d), - Body::Status(s) => self.server.enqueue_status(s), - }; - } - self.server.enqueue_status(response.completion); - return Ok(LoopMode::Interactive) - }, - // Session has some information for user - Some(ResponseOrIdle::IdleEvent(elems)) => { - tracing::trace!("server imap session has some change to communicate to the client"); - for body_elem in elems.into_iter() { - let _handle = match body_elem { - Body::Data(d) => self.server.enqueue_data(d), - Body::Status(s) => self.server.enqueue_status(s), - }; - } - self.cmd_tx.try_send(Request::Idle)?; - return Ok(LoopMode::Idle(buff, stop)) - }, - - // Session crashed - None => { - self.server.enqueue_status(Status::bye(None, "Internal session exited").unwrap()); - tracing::error!("session task exited for {:?}, quitting", self.ctx.addr); - return Ok(LoopMode::Interactive) - }, - - // Session can't start idling while already idling, it's a logic error! - Some(ResponseOrIdle::StartIdle(..)) => bail!("can't start idling while already idling!"), - }, - - // User is trying to interact with us - read_client_result = self.server.stream.read(&mut buff) => { - let _bytes_read = read_client_result?; - use imap_codec::decode::Decoder; - let codec = imap_codec::IdleDoneCodec::new(); - tracing::trace!("client sent some data for the server IMAP session"); - match codec.decode(&buff) { - Ok(([], imap_codec::imap_types::extensions::idle::IdleDone)) => { - // Session will be informed that it must stop idle - // It will generate the "done" message and change the loop mode - tracing::trace!("client sent DONE and want to stop IDLE"); - stop.notify_one() - }, - Err(_) => { - tracing::trace!("Unable to decode DONE, maybe not enough data were sent?"); - }, - _ => bail!("Client sent data after terminating the continuation without waiting for the server. This is an unsupported behavior and bug in Aerogramme, quitting."), - }; - - return Ok(LoopMode::Idle(buff, stop)) - }, - - // When receiving a CTRL+C - _ = self.ctx.must_exit.changed() => { - tracing::trace!("CTRL+C sent, aborting IDLE for this session"); - self.server.enqueue_status(Status::bye(None, "Server is being shutdown").unwrap()); - return Ok(LoopMode::Interactive) - }, - }; - }*/ } |