From 51c8f7b2796892bb3e365e725ccde9ad89b6103f Mon Sep 17 00:00:00 2001 From: mdecimus Date: Fri, 20 Sep 2024 08:18:51 +0200 Subject: [PATCH] IMAP IDLE support for command pipelining (fixes #765) --- crates/imap-proto/src/protocol/capability.rs | 4 +-- crates/imap/src/core/mod.rs | 4 --- crates/imap/src/core/session.rs | 10 +++++-- crates/imap/src/lib.rs | 31 +++++++++++++------- crates/imap/src/op/authenticate.rs | 5 +++- crates/imap/src/op/capability.rs | 2 +- crates/imap/src/op/idle.rs | 4 +-- 7 files changed, 36 insertions(+), 24 deletions(-) diff --git a/crates/imap-proto/src/protocol/capability.rs b/crates/imap-proto/src/protocol/capability.rs index 224471be..5928fa73 100644 --- a/crates/imap-proto/src/protocol/capability.rs +++ b/crates/imap-proto/src/protocol/capability.rs @@ -95,7 +95,7 @@ impl Capability { }); } - pub fn all_capabilities(is_authenticated: bool, is_tls: bool) -> Vec { + pub fn all_capabilities(is_authenticated: bool, offer_tls: bool) -> Vec { let mut capabilities = vec![ Capability::IMAP4rev2, Capability::IMAP4rev1, @@ -140,7 +140,7 @@ impl Capability { Capability::Auth(Mechanism::Plain), ]); } - if !is_tls { + if offer_tls { capabilities.push(Capability::StartTLS); } diff --git a/crates/imap/src/core/mod.rs b/crates/imap/src/core/mod.rs index 7af304b7..b8175d67 100644 --- a/crates/imap/src/core/mod.rs +++ b/crates/imap/src/core/mod.rs @@ -52,11 +52,7 @@ pub struct ImapInstance { } pub struct Inner { - pub greeting_plain: Vec, - pub greeting_tls: Vec, - pub rate_limiter: DashMap>, - pub cache_account: LruCache>, pub cache_mailbox: LruCache>, } diff --git a/crates/imap/src/core/session.rs b/crates/imap/src/core/session.rs index 25e43732..8284ed67 100644 --- a/crates/imap/src/core/session.rs +++ b/crates/imap/src/core/session.rs @@ -15,6 +15,8 @@ use jmap::JMAP; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio_rustls::server::TlsStream; +use crate::{GREETING_WITHOUT_TLS, GREETING_WITH_TLS}; + use super::{ImapSessionManager, Session, State}; impl SessionManager for ImapSessionManager { @@ -116,11 +118,13 @@ impl Session { manager: ImapSessionManager, ) -> Result, ()> { // Write greeting - let (is_tls, greeting) = if session.stream.is_tls() { - (true, &manager.imap.imap_inner.greeting_tls) + let is_tls = session.stream.is_tls(); + let greeting = if !is_tls && session.instance.acceptor.is_tls() { + &GREETING_WITH_TLS } else { - (false, &manager.imap.imap_inner.greeting_plain) + &GREETING_WITHOUT_TLS }; + if let Err(err) = session.stream.write_all(greeting).await { trc::event!( Network(trc::NetworkEvent::WriteError), diff --git a/crates/imap/src/lib.rs b/crates/imap/src/lib.rs index 8f83c85c..e4a0eccc 100644 --- a/crates/imap/src/lib.rs +++ b/crates/imap/src/lib.rs @@ -5,7 +5,10 @@ */ use core::{ImapInstance, Inner, IMAP}; -use std::{collections::hash_map::RandomState, sync::Arc}; +use std::{ + collections::hash_map::RandomState, + sync::{Arc, LazyLock}, +}; use dashmap::DashMap; use imap_proto::{protocol::capability::Capability, ResponseCode, StatusResponse}; @@ -20,6 +23,22 @@ pub mod op; static SERVER_GREETING: &str = "Stalwart IMAP4rev2 at your service."; +pub(crate) static GREETING_WITH_TLS: LazyLock> = LazyLock::new(|| { + StatusResponse::ok(SERVER_GREETING) + .with_code(ResponseCode::Capability { + capabilities: Capability::all_capabilities(false, true), + }) + .into_bytes() +}); + +pub(crate) static GREETING_WITHOUT_TLS: LazyLock> = LazyLock::new(|| { + StatusResponse::ok(SERVER_GREETING) + .with_code(ResponseCode::Capability { + capabilities: Capability::all_capabilities(false, false), + }) + .into_bytes() +}); + impl IMAP { pub async fn init(config: &mut Config, jmap_instance: JmapInstance) -> ImapInstance { let shard_amount = config @@ -29,16 +48,6 @@ impl IMAP { let capacity = config.property("cache.capacity").unwrap_or(100); let inner = Inner { - greeting_plain: StatusResponse::ok(SERVER_GREETING) - .with_code(ResponseCode::Capability { - capabilities: Capability::all_capabilities(false, false), - }) - .into_bytes(), - greeting_tls: StatusResponse::ok(SERVER_GREETING) - .with_code(ResponseCode::Capability { - capabilities: Capability::all_capabilities(false, true), - }) - .into_bytes(), rate_limiter: DashMap::with_capacity_and_hasher_and_shard_amount( capacity, RandomState::default(), diff --git a/crates/imap/src/op/authenticate.rs b/crates/imap/src/op/authenticate.rs index 88a31fec..5724c6af 100644 --- a/crates/imap/src/op/authenticate.rs +++ b/crates/imap/src/op/authenticate.rs @@ -140,7 +140,10 @@ impl Session { self.write_bytes( StatusResponse::ok("Authentication successful") .with_code(ResponseCode::Capability { - capabilities: Capability::all_capabilities(true, self.is_tls), + capabilities: Capability::all_capabilities( + true, + !self.is_tls && self.instance.acceptor.is_tls(), + ), }) .with_tag(tag) .into_bytes(), diff --git a/crates/imap/src/op/capability.rs b/crates/imap/src/op/capability.rs index fc6c163c..a8f3d7f5 100644 --- a/crates/imap/src/op/capability.rs +++ b/crates/imap/src/op/capability.rs @@ -39,7 +39,7 @@ impl Session { Response { capabilities: Capability::all_capabilities( self.state.is_authenticated(), - self.is_tls, + !self.is_tls && self.instance.acceptor.is_tls(), ), } .serialize(), diff --git a/crates/imap/src/op/idle.rs b/crates/imap/src/op/idle.rs index be97d88f..819311b9 100644 --- a/crates/imap/src/op/idle.rs +++ b/crates/imap/src/op/idle.rs @@ -69,10 +69,10 @@ impl Session { ); let op_start = Instant::now(); - let mut buf = vec![0; 1024]; + let mut buf = vec![0; 4]; loop { tokio::select! { - result = tokio::time::timeout(self.jmap.core.imap.timeout_idle, self.stream_rx.read(&mut buf)) => { + result = tokio::time::timeout(self.jmap.core.imap.timeout_idle, self.stream_rx.read_exact(&mut buf)) => { match result { Ok(Ok(bytes_read)) => { if bytes_read > 0 {