/* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ use ahash::AHashMap; use common::{ Inner, Server, auth::AccessToken, network::{ServerInstance, SessionStream, limiter::InFlight}, }; use imap_proto::{ Command, protocol::{ProtocolVersion, list::Attribute}, receiver::Receiver, }; use std::{ collections::BTreeMap, net::IpAddr, sync::{Arc, atomic::AtomicU32}, }; use tokio::{ io::{ReadHalf, WriteHalf}, sync::watch, }; use trc::AddContext; pub mod client; pub mod mailbox; pub mod message; pub mod session; #[derive(Clone)] pub struct ImapSessionManager { pub inner: Arc, } impl ImapSessionManager { pub fn new(inner: Arc) -> Self { Self { inner } } } pub struct Session { pub server: Server, pub instance: Arc, pub receiver: Receiver, pub version: ProtocolVersion, pub state: State, pub is_tls: bool, pub is_condstore: bool, pub is_qresync: bool, pub is_utf8: bool, pub is_objectid: bool, pub is_uidonly: bool, pub stream_rx: ReadHalf, pub stream_tx: Arc>>, pub in_flight: InFlight, pub remote_addr: IpAddr, pub session_id: u64, } pub struct SessionData { pub account_id: u32, pub access_token: AccessToken, pub server: Server, pub session_id: u64, pub mailboxes: parking_lot::Mutex>, pub stream_tx: Arc>>, pub state: AtomicU32, pub remote_addr: IpAddr, pub in_flight: Option, } pub struct SelectedMailbox { pub id: MailboxId, pub state: parking_lot::Mutex, pub saved_search: parking_lot::Mutex, pub is_select: bool, pub is_condstore: bool, } #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] pub struct MailboxId { pub account_id: u32, pub mailbox_id: u32, } #[derive(Debug, Clone, Default)] pub struct Account { pub account_id: u32, pub prefix: Option, pub mailbox_names: BTreeMap, pub mailbox_state: AHashMap, pub last_change_id: u64, } #[derive(Debug, Default, Clone)] pub struct Mailbox { pub has_children: bool, pub is_subscribed: bool, pub special_use: Option, pub total_messages: u64, pub total_unseen: u64, pub total_deleted: u64, pub total_deleted_storage: Option, pub uid_validity: u64, pub uid_next: u64, pub size: Option, } #[derive(Debug, Clone, Default)] pub struct MailboxState { pub uid_max: u32, pub id_to_imap: AHashMap, pub uid_to_id: AHashMap, pub total_messages: usize, pub modseq: u64, pub next_state: Option>, } #[derive(Debug, Clone)] pub struct NextMailboxState { pub next_state: MailboxState, pub deletions: Vec, } #[derive(Debug, Clone, Copy, Default)] pub struct ImapId { pub uid: u32, pub seqnum: u32, } #[derive(Debug, Default)] pub struct MailboxSync { pub added: Vec, pub changed: Vec, pub deleted: Vec, } pub enum SavedSearch { InFlight { rx: watch::Receiver>>, }, Results { items: Arc>, }, None, } #[derive(Debug, Clone, Copy, Default)] pub struct ImapUidToId { pub uid: u32, pub id: u32, } pub enum State { NotAuthenticated { auth_failures: u32, }, Authenticated { data: Arc>, }, Selected { data: Arc>, mailbox: Arc, }, } impl State { pub fn try_replace_stream_tx( self, new_stream: Arc>>, ) -> Option> { match self { State::NotAuthenticated { auth_failures } => { State::NotAuthenticated { auth_failures }.into() } State::Authenticated { data } => { Arc::try_unwrap(data).ok().map(|data| State::Authenticated { data: Arc::new(data.replace_stream_tx(new_stream)), }) } State::Selected { data, mailbox } => { Arc::try_unwrap(data).ok().map(|data| State::Selected { data: Arc::new(data.replace_stream_tx(new_stream)), mailbox, }) } } } } impl SessionData { pub async fn refresh_access_token(&self) -> trc::Result { self.server .access_token(self.account_id) .await .and_then(|inner| { AccessToken::renew(inner, self.access_token.credential_id(), self.remote_addr) }) .caused_by(trc::location!()) } pub fn replace_stream_tx( self, new_stream: Arc>>, ) -> SessionData { SessionData { account_id: self.account_id, server: self.server, session_id: self.session_id, mailboxes: self.mailboxes, stream_tx: new_stream, state: self.state, in_flight: self.in_flight, access_token: self.access_token, remote_addr: self.remote_addr, } } } impl MailboxState { pub fn map_result_id(&self, document_id: u32, is_uid: bool) -> Option<(u32, ImapId)> { if let Some(imap_id) = self.id_to_imap.get(&document_id) { Some((if is_uid { imap_id.uid } else { imap_id.seqnum }, *imap_id)) } else if is_uid { self.next_state.as_ref().and_then(|s| { s.next_state .id_to_imap .get(&document_id) .map(|imap_id| (imap_id.uid, *imap_id)) }) } else { None } } }