IMAP IDLE support for command pipelining (fixes #765)

This commit is contained in:
mdecimus 2024-09-20 08:18:51 +02:00
parent 180a856cb4
commit 51c8f7b279
7 changed files with 36 additions and 24 deletions

View file

@ -95,7 +95,7 @@ impl Capability {
});
}
pub fn all_capabilities(is_authenticated: bool, is_tls: bool) -> Vec<Capability> {
pub fn all_capabilities(is_authenticated: bool, offer_tls: bool) -> Vec<Capability> {
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);
}

View file

@ -52,11 +52,7 @@ pub struct ImapInstance {
}
pub struct Inner {
pub greeting_plain: Vec<u8>,
pub greeting_tls: Vec<u8>,
pub rate_limiter: DashMap<u32, Arc<ConcurrencyLimiters>>,
pub cache_account: LruCache<AccountId, Arc<Account>>,
pub cache_mailbox: LruCache<MailboxId, Arc<MailboxState>>,
}

View file

@ -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<T: SessionStream> Session<T> {
manager: ImapSessionManager,
) -> Result<Session<T>, ()> {
// 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),

View file

@ -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<Vec<u8>> = 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<Vec<u8>> = 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(),

View file

@ -140,7 +140,10 @@ impl<T: SessionStream> Session<T> {
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(),

View file

@ -39,7 +39,7 @@ impl<T: SessionStream> Session<T> {
Response {
capabilities: Capability::all_capabilities(
self.state.is_authenticated(),
self.is_tls,
!self.is_tls && self.instance.acceptor.is_tls(),
),
}
.serialize(),

View file

@ -69,10 +69,10 @@ impl<T: SessionStream> Session<T> {
);
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 {