diff --git a/src/command.rs b/src/command.rs index 0b576b2..4391b39 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1,5 +1,6 @@ -use boitalettres::errors::Error as BalError; +use anyhow::Result; use boitalettres::proto::{Request, Response}; +use boitalettres::errors::Error as BalError; use imap_codec::types::core::{Tag, AString}; use imap_codec::types::response::{Capability, Data}; use imap_codec::types::mailbox::{Mailbox as MailboxCodec, ListMailbox}; @@ -20,7 +21,7 @@ impl<'a> Command<'a> { Self { tag, session } } - pub async fn capability(&self) -> Result { + 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.")? @@ -28,52 +29,68 @@ impl<'a> Command<'a> { Ok(r) } - pub async fn login(&mut self, username: AString, password: AString) -> Result { - let (u, p) = match (String::try_from(username), String::try_from(password)) { - (Ok(u), Ok(p)) => (u, p), - _ => return Response::bad("Invalid characters"), - }; + pub async fn login(&mut self, username: AString, password: AString) -> Result { + let (u, p) = (String::try_from(username)?, String::try_from(password)?); + tracing::info!(user = %u, "command.login"); - tracing::debug!(user = %u, "command.login"); let creds = match self.session.mailstore.login_provider.login(&u, &p).await { - Err(_) => return Response::no("[AUTHENTICATIONFAILED] Authentication failed."), + Err(_) => return Ok(Response::no("[AUTHENTICATIONFAILED] Authentication failed.")?), Ok(c) => c, }; - self.session.creds = Some(creds); - self.session.username = Some(u.clone()); + self.session.user = Some(session::User { creds, name: u.clone(), }); tracing::info!(username=%u, "connected"); - Response::ok("Logged in") + Ok(Response::ok("Logged in")?) } - pub async fn lsub(&self, reference: MailboxCodec, mailbox_wildcard: ListMailbox) -> Result { - Response::bad("Not implemented") + pub async fn lsub(&self, reference: MailboxCodec, mailbox_wildcard: ListMailbox) -> Result { + Ok(Response::bad("Not implemented")?) } - pub async fn list(&self, reference: MailboxCodec, mailbox_wildcard: ListMailbox) -> Result { - Response::bad("Not implemented") + pub async fn list(&self, reference: MailboxCodec, mailbox_wildcard: ListMailbox) -> Result { + Ok(Response::bad("Not implemented")?) } - pub async fn select(&mut self, mailbox: MailboxCodec) -> Result { - let (name, creds) = match (String::try_from(mailbox), self.session.creds.as_ref()) { - (Ok(n), Some(c)) => (n, c), - (_, None) => return Response::no("You must be connected to use SELECT"), - (Err(e), _) => { - tracing::warn!("Unable to decode mailbox name: {:#?}", e); - return Response::bad("Unable to decode mailbox name") - }, + /* + * TRACE BEGIN --- + + + Example: C: A142 SELECT INBOX + S: * 172 EXISTS + S: * 1 RECENT + S: * OK [UNSEEN 12] Message 12 is first unseen + S: * OK [UIDVALIDITY 3857529045] UIDs valid + S: * OK [UIDNEXT 4392] Predicted next UID + S: * FLAGS (\Answered \Flagged \Deleted \Seen \Draft) + S: * OK [PERMANENTFLAGS (\Deleted \Seen \*)] Limited + S: A142 OK [READ-WRITE] SELECT completed + + * TRACE END --- + */ + pub async fn select(&mut self, mailbox: MailboxCodec) -> Result { + 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")?), }; - let mb = Mailbox::new(creds, name.clone()).unwrap(); - self.session.selected = Some(mb); - let user = self.session.username.as_ref().unwrap(); + let mut mb = Mailbox::new(&user.creds, name.clone())?; + tracing::info!(username=%user.name, mailbox=%name, "mailbox.selected"); - tracing::info!(username=%user, mailbox=%name, "mailbox-selected"); - Response::bad("Not implemented") + let sum = mb.summary().await?; + tracing::trace!(summary=%sum, "mailbox.summary"); + + 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)) } - pub async fn fetch(&self, sequence_set: SequenceSet, attributes: MacroOrFetchAttributes, uid: bool) -> Result { - Response::bad("Not implemented") + pub async fn fetch(&self, sequence_set: SequenceSet, attributes: MacroOrFetchAttributes, uid: bool) -> Result { + Ok(Response::bad("Not implemented")?) } } diff --git a/src/mailbox.rs b/src/mailbox.rs index af71b91..3b1ac99 100644 --- a/src/mailbox.rs +++ b/src/mailbox.rs @@ -8,6 +8,17 @@ use crate::cryptoblob::Key; use crate::login::Credentials; use crate::uidindex::*; +pub struct Summary { + pub validity: ImapUidvalidity, + pub next: ImapUid, + pub exists: usize, +} +impl std::fmt::Display for Summary { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "uidvalidity: {}, uidnext: {}, exists: {}", self.validity, self.next, self.exists) + } +} + pub struct Mailbox { bucket: String, pub name: String, @@ -33,6 +44,17 @@ impl Mailbox { }) } + pub async fn summary(&mut self) -> Result { + self.uid_index.sync().await?; + let state = self.uid_index.state(); + + return Ok(Summary { + validity: state.uidvalidity, + next: state.uidnext, + exists: state.mail_uid.len(), + }) + } + pub async fn test(&mut self) -> Result<()> { self.uid_index.sync().await?; diff --git a/src/session.rs b/src/session.rs index 78d5ffc..d689e72 100644 --- a/src/session.rs +++ b/src/session.rs @@ -66,17 +66,21 @@ impl Manager { } } +pub struct User { + pub name: String, + pub creds: Credentials, +} + pub struct Instance { rx: mpsc::Receiver, pub mailstore: Arc, - pub creds: Option, pub selected: Option, - pub username: Option, + pub user: Option, } impl Instance { fn new(mailstore: Arc, rx: mpsc::Receiver) -> Self { - Self { mailstore, rx, creds: None, selected: None, username: None, } + Self { mailstore, rx, selected: None, user: None, } } //@FIXME add a function that compute the runner's name from its local info @@ -96,12 +100,20 @@ impl Instance { CommandBody::List { reference, mailbox_wildcard } => cmd.list(reference, mailbox_wildcard).await, CommandBody::Select { mailbox } => cmd.select(mailbox).await, CommandBody::Fetch { sequence_set, attributes, uid } => cmd.fetch(sequence_set, attributes, uid).await, - _ => Response::bad("Error in IMAP command received by server."), + _ => Response::bad("Error in IMAP command received by server.").map_err(anyhow::Error::new), }; + 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") + } + }); + //@FIXME I think we should quit this thread on error and having our manager watch it, // and then abort the session as it is corrupted. - msg.tx.send(res).unwrap_or_else(|e| tracing::warn!("failed to send imap response to manager: {:#?}", e)); + msg.tx.send(wrapped_res).unwrap_or_else(|e| tracing::warn!("failed to send imap response to manager: {:#?}", e)); } //@FIXME add more info about the runner diff --git a/src/uidindex.rs b/src/uidindex.rs index 1e30190..42aa3bc 100644 --- a/src/uidindex.rs +++ b/src/uidindex.rs @@ -3,8 +3,8 @@ use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer}; use crate::bayou::*; -type ImapUid = u32; -type ImapUidvalidity = u32; +pub type ImapUid = u32; +pub type ImapUidvalidity = u32; /// A Mail UUID is composed of two components: /// - a process identifier, 128 bits @@ -19,6 +19,7 @@ pub struct UidIndex { pub mails_by_uid: OrdMap, + pub uidvalidity: ImapUidvalidity, pub uidnext: ImapUid, pub internalseq: ImapUid,