diff --git a/Cargo.toml b/Cargo.toml index 1cc143e..557be55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,7 +58,7 @@ aws-sdk-s3 = "1.9.0" eml-codec = { git = "https://git.deuxfleurs.fr/Deuxfleurs/eml-codec.git", branch = "main" } 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 = "1.0.0", features = ["quirk_crlf_relaxed", "bounded-static"] } +imap-codec = { version = "1.0.0", features = ["quirk_crlf_relaxed", "bounded-static", "ext_condstore_qresync"] } imap-flow = { git = "https://github.com/duesee/imap-flow.git", rev = "e45ce7bb6ab6bda3c71a0c7b05e9b558a5902e90" } [dev-dependencies] diff --git a/src/imap/capability.rs b/src/imap/capability.rs index b98e8f8..f6b5a51 100644 --- a/src/imap/capability.rs +++ b/src/imap/capability.rs @@ -1,10 +1,24 @@ use imap_codec::imap_types::core::NonEmptyVec; use imap_codec::imap_types::response::Capability; +fn capability_unselect() -> Capability<'static> { + Capability::try_from("UNSELECT").unwrap() +} + +fn capability_condstore() -> Capability<'static> { + Capability::try_from("CONDSTORE").unwrap() +} + +fn capability_qresync() -> Capability<'static> { + Capability::try_from("QRESYNC").unwrap() +} + #[derive(Debug, Clone)] pub struct ServerCapability { r#move: bool, unselect: bool, + condstore: bool, + qresync: bool, } impl Default for ServerCapability { @@ -12,6 +26,8 @@ impl Default for ServerCapability { Self { r#move: true, unselect: true, + condstore: false, + qresync: false, } } } @@ -23,8 +39,64 @@ impl ServerCapability { acc.push(Capability::Move); } if self.unselect { - acc.push(Capability::try_from("UNSELECT").unwrap()); + acc.push(capability_unselect()); + } + if self.condstore { + acc.push(capability_condstore()); + } + if self.qresync { + acc.push(capability_qresync()); } acc.try_into().unwrap() } + + pub fn support(&self, cap: &Capability<'static>) -> bool { + match cap { + Capability::Imap4Rev1 => true, + Capability::Move => self.r#move, + x if *x == capability_condstore() => self.condstore, + x if *x == capability_qresync() => self.qresync, + x if *x == capability_unselect() => self.unselect, + _ => false, + } + } +} + +pub struct ClientCapability { + condstore: bool, + qresync: bool, +} + +impl Default for ClientCapability { + fn default() -> Self { + Self { + condstore: false, + qresync: false, + } + } +} + +impl ClientCapability { + pub fn try_enable( + &mut self, + srv: &ServerCapability, + caps: &[Capability<'static>], + ) -> Vec> { + let mut enabled = vec![]; + for cap in caps { + match cap { + x if *x == capability_condstore() && srv.condstore && !self.condstore => { + self.condstore = true; + enabled.push(x.clone()); + } + x if *x == capability_qresync() && srv.qresync && !self.qresync => { + self.qresync = true; + enabled.push(x.clone()); + } + _ => (), + } + } + + enabled + } } diff --git a/src/imap/command/authenticated.rs b/src/imap/command/authenticated.rs index 2970b63..fbf29f9 100644 --- a/src/imap/command/authenticated.rs +++ b/src/imap/command/authenticated.rs @@ -305,6 +305,9 @@ impl<'a> AuthenticatedContext<'a> { StatusDataItemName::DeletedStorage => { bail!("quota not implemented, can't return freed storage after EXPUNGE will be run"); }, + StatusDataItemName::HighestModSeq => { + bail!("highestmodseq not yet implemented"); + } }); } @@ -524,7 +527,7 @@ impl<'a> AuthenticatedContext<'a> { }; if date.is_some() { - bail!("Cannot set date when appending message"); + tracing::warn!("Cannot set date when appending message"); } let msg = diff --git a/src/imap/mod.rs b/src/imap/mod.rs index 0b5555a..aac1fd3 100644 --- a/src/imap/mod.rs +++ b/src/imap/mod.rs @@ -89,7 +89,7 @@ async fn client_wrapper(ctx: ClientContext) { let addr = ctx.addr.clone(); match client(ctx).await { Ok(()) => { - tracing::info!("closing successful session for {:?}", addr); + tracing::debug!("closing successful session for {:?}", addr); } Err(e) => { tracing::error!("closing errored session for {:?}: {}", addr, e); @@ -127,7 +127,9 @@ async fn client(mut ctx: ClientContext) -> Result<()> { Some(cmd_recv) => cmd_recv, }; + tracing::debug!(cmd=?cmd, sock=%ctx.addr, "command"); let maybe_response = session.command(cmd).await; + tracing::debug!(cmd=?maybe_response.completion, sock=%ctx.addr, "response"); match resp_tx.send(maybe_response) { Err(_) => break, diff --git a/src/mail/incoming.rs b/src/mail/incoming.rs index 04d2ef1..781d8dc 100644 --- a/src/mail/incoming.rs +++ b/src/mail/incoming.rs @@ -9,7 +9,7 @@ use base64::Engine; use futures::{future::BoxFuture, FutureExt}; //use tokio::io::AsyncReadExt; use tokio::sync::watch; -use tracing::{error, info, warn}; +use tracing::{debug, error, info, warn}; use crate::cryptoblob; use crate::login::{Credentials, PublicCredentials}; @@ -62,7 +62,7 @@ async fn incoming_mail_watch_process_internal( loop { let maybe_updated_incoming_key = if *lock_held.borrow() { - info!("incoming lock held"); + debug!("incoming lock held"); let wait_new_mail = async { loop { @@ -83,7 +83,7 @@ async fn incoming_mail_watch_process_internal( _ = rx_inbox_id.changed() => None, } } else { - info!("incoming lock not held"); + debug!("incoming lock not held"); tokio::select! { _ = lock_held.changed() => None, _ = rx_inbox_id.changed() => None, @@ -93,11 +93,11 @@ async fn incoming_mail_watch_process_internal( let user = match Weak::upgrade(&user) { Some(user) => user, None => { - info!("User no longer available, exiting incoming loop."); + debug!("User no longer available, exiting incoming loop."); break; } }; - info!("User still available"); + debug!("User still available"); // If INBOX no longer is same mailbox, open new mailbox let inbox_id = *rx_inbox_id.borrow(); @@ -235,7 +235,7 @@ async fn k2v_lock_loop_internal( let watch_lock_loop: BoxFuture> = async { let mut ct = row_ref.clone(); loop { - info!("k2v watch lock loop iter: ct = {:?}", ct); + debug!("k2v watch lock loop iter: ct = {:?}", ct); match storage.row_poll(&ct).await { Err(e) => { error!( @@ -263,7 +263,7 @@ async fn k2v_lock_loop_internal( } let new_ct = cv.row_ref; - info!( + debug!( "k2v watch lock loop: changed, old ct = {:?}, new ct = {:?}, v = {:?}", ct, new_ct, lock_state ); @@ -378,7 +378,7 @@ async fn k2v_lock_loop_internal( let _ = futures::try_join!(watch_lock_loop, lock_notify_loop, take_lock_loop); - info!("lock loop exited, releasing"); + debug!("lock loop exited, releasing"); if !held_tx.is_closed() { warn!("weird...");