/* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL */ use protocol::ObjectId; use protocol::capability::Capability; use std::borrow::Cow; pub mod parser; pub mod protocol; pub mod receiver; pub mod utf7; #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum Command { // Client Commands - Any State Capability, #[default] Noop, Logout, // Client Commands - Not Authenticated State StartTls, Authenticate, Login, // Client Commands - Authenticated State Enable, Select, Examine, Create, Delete, Rename, Subscribe, Unsubscribe, List, Namespace, Status, Append, Idle, // Client Commands - Selected State Close, Unselect, Expunge(bool), Search(bool), Fetch(bool), Store(bool), Copy(bool), Move(bool), // IMAP4rev1 Lsub, Check, // RFC 5256 Sort(bool), Thread(bool), // RFC 4314 SetAcl, DeleteAcl, GetAcl, ListRights, MyRights, // RFC 8437 Unauthenticate, // RFC 2971 Id, // RFC 9208 GetQuota, GetQuotaRoot, // RFC 9698 GetJmapAccess, // RFC 10022 UidBatches, } impl Command { pub fn is_uid(&self) -> bool { matches!( self, Command::Fetch(true) | Command::Search(true) | Command::Copy(true) | Command::Move(true) | Command::Store(true) | Command::Expunge(true) | Command::Sort(true) | Command::Thread(true) ) } pub fn requires_uid(&self) -> bool { matches!( self, Command::Fetch(false) | Command::Search(false) | Command::Copy(false) | Command::Move(false) | Command::Store(false) | Command::Sort(false) | Command::Thread(false) ) } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum ResponseCode { Alert, AlreadyExists, AppendUid { uid_validity: u32, uids: Vec, }, AuthenticationFailed, AuthorizationFailed, BadCharset, Cannot, Capability { capabilities: Vec, }, ClientBug, Closed, ContactAdmin, CopyUid { uid_validity: u32, src_uids: Vec, dest_uids: Vec, }, Corruption, Expired, ExpungeIssued, HasChildren, InUse, Limit, NonExistent, NoPerm, OverQuota, Parse, PermanentFlags, PrivacyRequired, ReadOnly, ReadWrite, ServerBug, TryCreate, UidNext, UidNotSticky, UidValidity, Unavailable, UnknownCte, // CONDSTORE Modified { ids: Vec, }, HighestModseq { modseq: u64, }, // ObjectID ObjectId(ObjectId), // USEATTR UseAttr, // UIDONLY UidRequired, // UIDBATCHES TooFew, TooMany, // MESSAGELIMIT MessageLimit { limit: u32, uid: Option, }, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct StatusResponse { pub tag: Option, pub code: Option, pub message: Cow<'static, str>, pub rtype: ResponseType, } #[derive(Debug, Clone, PartialEq, Eq)] pub enum ResponseType { Ok, No, Bad, PreAuth, Bye, } impl ResponseCode { pub fn highest_modseq(modseq: u64) -> Self { ResponseCode::HighestModseq { modseq: if modseq > 0 { modseq + 1 } else { 0 }, } } } impl StatusResponse { pub fn bad(message: impl Into>) -> Self { StatusResponse { tag: None, code: None, message: message.into(), rtype: ResponseType::Bad, } } pub fn parse_error(message: impl Into>) -> Self { StatusResponse { tag: None, code: ResponseCode::Parse.into(), message: message.into(), rtype: ResponseType::Bad, } } pub fn database_failure() -> Self { StatusResponse::no("Database failure.").with_code(ResponseCode::ContactAdmin) } pub fn completed(command: Command) -> Self { StatusResponse::ok(format!("{} completed", command)) } pub fn with_code(mut self, code: ResponseCode) -> Self { self.code = Some(code); self } pub fn with_tag(mut self, tag: impl Into) -> Self { self.tag = Some(tag.into()); self } pub fn no(message: impl Into>) -> Self { StatusResponse { tag: None, code: None, message: message.into(), rtype: ResponseType::No, } } pub fn ok(message: impl Into>) -> Self { StatusResponse { tag: None, code: None, message: message.into(), rtype: ResponseType::Ok, } } pub fn bye(message: impl Into>) -> Self { StatusResponse { tag: None, code: None, message: message.into(), rtype: ResponseType::Bye, } } }