diff --git a/Cargo.lock b/Cargo.lock index a467936..4b9e9ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -362,7 +362,7 @@ dependencies = [ [[package]] name = "boitalettres" version = "0.0.1" -source = "git+https://git.deuxfleurs.fr/KokaKiwi/boitalettres.git?branch=main#fc5f09356466d51404317c1b09e19720dd50c314" +source = "git+https://git.deuxfleurs.fr/quentin/boitalettres.git?branch=expose_req_res#2c43b7686a7cd06f733719350bd61f792d20338e" dependencies = [ "async-compat", "bytes", diff --git a/Cargo.toml b/Cargo.toml index 4393d1c..2cbf5f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,7 @@ tower = "0.4" imap-codec = "0.5" k2v-client = { git = "https://git.deuxfleurs.fr/Deuxfleurs/garage.git", branch = "main" } -boitalettres = { git = "https://git.deuxfleurs.fr/KokaKiwi/boitalettres.git", branch = "main" } +boitalettres = { git = "https://git.deuxfleurs.fr/quentin/boitalettres.git", branch = "expose_req_res" } smtp-message = { git = "http://github.com/Alexis211/kannader", branch = "feature/lmtp" } smtp-server = { git = "http://github.com/Alexis211/kannader", branch = "feature/lmtp" } diff --git a/src/command.rs b/src/command.rs index 4a2723d..77b780a 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1,10 +1,10 @@ -use anyhow::Result; +use anyhow::{Error, Result}; use boitalettres::errors::Error as BalError; use boitalettres::proto::{Request, Response}; use imap_codec::types::core::{AString, Tag}; use imap_codec::types::fetch_attributes::MacroOrFetchAttributes; use imap_codec::types::mailbox::{ListMailbox, Mailbox as MailboxCodec}; -use imap_codec::types::response::{Capability, Data}; +use imap_codec::types::response::{Capability, Code, Data, Response as ImapRes, Status}; use imap_codec::types::sequence::SequenceSet; use crate::mailbox::Mailbox; @@ -22,10 +22,14 @@ impl<'a> Command<'a> { pub async fn capability(&self) -> Result { let capabilities = vec![Capability::Imap4Rev1, Capability::Idle]; - let body = vec![Data::Capability(capabilities)]; - let r = Response::ok("Pre-login capabilities listed, post-login capabilities have more.")? - .with_body(body); - Ok(r) + let res = vec![ + ImapRes::Data(Data::Capability(capabilities)), + ImapRes::Status( + Status::ok(Some(self.tag.clone()), None, "Server capabilities") + .map_err(Error::msg)?, + ), + ]; + Ok(res) } pub async fn login(&mut self, username: AString, password: AString) -> Result { @@ -33,10 +37,12 @@ impl<'a> Command<'a> { tracing::info!(user = %u, "command.login"); let creds = match self.session.login_provider.login(&u, &p).await { - Err(_) => { - return Ok(Response::no( - "[AUTHENTICATIONFAILED] Authentication failed.", - )?) + Err(e) => { + tracing::debug!(error=%e, "authentication failed"); + return Ok(vec![ImapRes::Status( + Status::no(Some(self.tag.clone()), None, "Authentication failed") + .map_err(Error::msg)?, + )]); } Ok(c) => c, }; @@ -47,7 +53,12 @@ impl<'a> Command<'a> { }); tracing::info!(username=%u, "connected"); - Ok(Response::ok("Logged in")?) + Ok(vec![ + //@FIXME we could send a capability status here too + ImapRes::Status( + Status::ok(Some(self.tag.clone()), None, "completed").map_err(Error::msg)?, + ), + ]) } pub async fn lsub( @@ -55,7 +66,9 @@ impl<'a> Command<'a> { reference: MailboxCodec, mailbox_wildcard: ListMailbox, ) -> Result { - Ok(Response::bad("Not implemented")?) + Ok(vec![ImapRes::Status( + Status::bad(Some(self.tag.clone()), None, "Not implemented").map_err(Error::msg)?, + )]) } pub async fn list( @@ -63,7 +76,9 @@ impl<'a> Command<'a> { reference: MailboxCodec, mailbox_wildcard: ListMailbox, ) -> Result { - Ok(Response::bad("Not implemented")?) + Ok(vec![ImapRes::Status( + Status::bad(Some(self.tag.clone()), None, "Not implemented").map_err(Error::msg)?, + )]) } /* @@ -86,7 +101,12 @@ impl<'a> Command<'a> { let name = String::try_from(mailbox)?; let user = match self.session.user.as_ref() { Some(u) => u, - _ => return Ok(Response::no("You must be connected to use SELECT")?), + _ => { + return Ok(vec![ImapRes::Status( + Status::no(Some(self.tag.clone()), None, "Not implemented") + .map_err(Error::msg)?, + )]) + } }; let mut mb = Mailbox::new(&user.creds, name.clone())?; @@ -98,7 +118,14 @@ impl<'a> Command<'a> { let body = vec![Data::Exists(sum.exists.try_into()?), Data::Recent(0)]; self.session.selected = Some(mb); - Ok(Response::ok("[READ-WRITE] Select completed")?.with_body(body)) + Ok(vec![ImapRes::Status( + Status::ok( + Some(self.tag.clone()), + Some(Code::ReadWrite), + "Select completed", + ) + .map_err(Error::msg)?, + )]) } pub async fn fetch( @@ -107,6 +134,8 @@ impl<'a> Command<'a> { attributes: MacroOrFetchAttributes, uid: bool, ) -> Result { - Ok(Response::bad("Not implemented")?) + Ok(vec![ImapRes::Status( + Status::bad(Some(self.tag.clone()), None, "Not implemented").map_err(Error::msg)?, + )]) } } diff --git a/src/session.rs b/src/session.rs index a709986..c74d3cd 100644 --- a/src/session.rs +++ b/src/session.rs @@ -1,10 +1,12 @@ use std::sync::Arc; +use anyhow::Error; use boitalettres::errors::Error as BalError; use boitalettres::proto::{Request, Response}; use futures::future::BoxFuture; use futures::future::FutureExt; use imap_codec::types::command::CommandBody; +use imap_codec::types::response::{Capability, Code, Data, Response as ImapRes, Status}; use tokio::sync::mpsc::error::TrySendError; use tokio::sync::{mpsc, oneshot}; @@ -41,6 +43,7 @@ impl Manager { pub fn process(&self, req: Request) -> BoxFuture<'static, Result> { let (tx, rx) = oneshot::channel(); + let tag = req.tag.clone(); let msg = Message { req, tx }; // We use try_send on a bounded channel to protect the daemons from DoS. @@ -50,10 +53,20 @@ impl Manager { match self.tx.try_send(msg) { Ok(()) => (), Err(TrySendError::Full(_)) => { - return async { Response::bad("Too fast! Send less pipelined requests!") }.boxed() + return async { + Status::bad(Some(tag), None, "Too fast! Send less pipelined requests!") + .map(|s| vec![ImapRes::Status(s)]) + .map_err(|e| BalError::Text(e.to_string())) + } + .boxed() } Err(TrySendError::Closed(_)) => { - return async { Response::bad("The session task has exited") }.boxed() + return async { + Status::bad(Some(tag), None, "The session task has exited") + .map(|s| vec![ImapRes::Status(s)]) + .map_err(|e| BalError::Text(e.to_string())) + } + .boxed() } }; @@ -63,7 +76,9 @@ impl Manager { Ok(r) => r, Err(e) => { tracing::warn!("Got error {:#?}", e); - Response::bad("No response from the session handler") + Status::bad(Some(tag), None, "No response from the session handler") + .map(|s| vec![ImapRes::Status(s)]) + .map_err(|e| BalError::Text(e.to_string())) } } } @@ -105,7 +120,7 @@ impl Instance { tracing::debug!("starting runner"); while let Some(msg) = self.rx.recv().await { - let mut cmd = command::Command::new(msg.req.tag, self); + let mut cmd = command::Command::new(msg.req.tag.clone(), self); let res = match msg.req.body { CommandBody::Capability => cmd.capability().await, CommandBody::Login { username, password } => cmd.login(username, password).await, @@ -123,15 +138,18 @@ impl Instance { attributes, uid, } => cmd.fetch(sequence_set, attributes, uid).await, - _ => Response::bad("Error in IMAP command received by server.") - .map_err(anyhow::Error::new), + _ => Status::bad(Some(msg.req.tag.clone()), None, "Unknown command") + .map(|s| vec![ImapRes::Status(s)]) + .map_err(Error::msg), }; let wrapped_res = res.or_else(|e| match e.downcast::() { Ok(be) => Err(be), Err(ae) => { tracing::warn!(error=%ae, "internal.error"); - Response::bad("Internal error") + Status::bad(Some(msg.req.tag.clone()), None, "Internal error") + .map(|s| vec![ImapRes::Status(s)]) + .map_err(|e| BalError::Text(e.to_string())) } });