compile with imap-flow
This commit is contained in:
parent
9a8d4c651e
commit
0d667a3030
11 changed files with 313 additions and 310 deletions
|
@ -1,7 +1,6 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use imap_codec::imap_types::command::{Command, CommandBody};
|
use imap_codec::imap_types::command::{Command, CommandBody};
|
||||||
use imap_codec::imap_types::core::{AString, NonEmptyVec};
|
use imap_codec::imap_types::core::AString;
|
||||||
use imap_codec::imap_types::response::{Capability, Data};
|
|
||||||
use imap_codec::imap_types::secret::Secret;
|
use imap_codec::imap_types::secret::Secret;
|
||||||
|
|
||||||
use crate::imap::command::anystate;
|
use crate::imap::command::anystate;
|
||||||
|
@ -13,16 +12,16 @@ use crate::mail::user::User;
|
||||||
//--- dispatching
|
//--- dispatching
|
||||||
|
|
||||||
pub struct AnonymousContext<'a> {
|
pub struct AnonymousContext<'a> {
|
||||||
pub req: &'a Command<'a>,
|
pub req: &'a Command<'static>,
|
||||||
pub login_provider: &'a ArcLoginProvider,
|
pub login_provider: &'a ArcLoginProvider,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn dispatch<'a>(ctx: AnonymousContext<'a>) -> Result<(Response<'a>, flow::Transition)> {
|
pub async fn dispatch(ctx: AnonymousContext<'_>) -> Result<(Response<'static>, flow::Transition)> {
|
||||||
match &ctx.req.body {
|
match &ctx.req.body {
|
||||||
// Any State
|
// Any State
|
||||||
CommandBody::Noop => anystate::noop_nothing(ctx.req.tag.clone()),
|
CommandBody::Noop => anystate::noop_nothing(ctx.req.tag.clone()),
|
||||||
CommandBody::Capability => anystate::capability(ctx.req.tag.clone()),
|
CommandBody::Capability => anystate::capability(ctx.req.tag.clone()),
|
||||||
CommandBody::Logout => Ok((Response::bye()?, flow::Transition::Logout)),
|
CommandBody::Logout => anystate::logout(),
|
||||||
|
|
||||||
// Specific to anonymous context (3 commands)
|
// Specific to anonymous context (3 commands)
|
||||||
CommandBody::Login { username, password } => ctx.login(username, password).await,
|
CommandBody::Login { username, password } => ctx.login(username, password).await,
|
||||||
|
@ -39,22 +38,11 @@ pub async fn dispatch<'a>(ctx: AnonymousContext<'a>) -> Result<(Response<'a>, fl
|
||||||
//--- Command controllers, private
|
//--- Command controllers, private
|
||||||
|
|
||||||
impl<'a> AnonymousContext<'a> {
|
impl<'a> AnonymousContext<'a> {
|
||||||
async fn capability(self) -> Result<(Response<'a>, flow::Transition)> {
|
|
||||||
let capabilities: NonEmptyVec<Capability> =
|
|
||||||
(vec![Capability::Imap4Rev1, Capability::Idle]).try_into()?;
|
|
||||||
let res = Response::build()
|
|
||||||
.to_req(self.req)
|
|
||||||
.message("Server capabilities")
|
|
||||||
.data(Data::Capability(capabilities))
|
|
||||||
.ok()?;
|
|
||||||
Ok((res, flow::Transition::None))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn login(
|
async fn login(
|
||||||
self,
|
self,
|
||||||
username: &AString<'a>,
|
username: &AString<'a>,
|
||||||
password: &Secret<AString<'a>>,
|
password: &Secret<AString<'a>>,
|
||||||
) -> Result<(Response<'a>, flow::Transition)> {
|
) -> Result<(Response<'static>, flow::Transition)> {
|
||||||
let (u, p) = (
|
let (u, p) = (
|
||||||
std::str::from_utf8(username.as_ref())?,
|
std::str::from_utf8(username.as_ref())?,
|
||||||
std::str::from_utf8(password.declassify().as_ref())?,
|
std::str::from_utf8(password.declassify().as_ref())?,
|
||||||
|
|
|
@ -5,7 +5,7 @@ use imap_codec::imap_types::response::{Capability, Data};
|
||||||
use crate::imap::flow;
|
use crate::imap::flow;
|
||||||
use crate::imap::response::Response;
|
use crate::imap::response::Response;
|
||||||
|
|
||||||
pub(crate) fn capability<'a>(tag: Tag<'a>) -> Result<(Response<'a>, flow::Transition)> {
|
pub(crate) fn capability(tag: Tag<'static>) -> Result<(Response<'static>, flow::Transition)> {
|
||||||
let capabilities: NonEmptyVec<Capability> =
|
let capabilities: NonEmptyVec<Capability> =
|
||||||
(vec![Capability::Imap4Rev1, Capability::Idle]).try_into()?;
|
(vec![Capability::Imap4Rev1, Capability::Idle]).try_into()?;
|
||||||
let res = Response::build()
|
let res = Response::build()
|
||||||
|
@ -17,7 +17,7 @@ pub(crate) fn capability<'a>(tag: Tag<'a>) -> Result<(Response<'a>, flow::Transi
|
||||||
Ok((res, flow::Transition::None))
|
Ok((res, flow::Transition::None))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn noop_nothing<'a>(tag: Tag<'a>) -> Result<(Response<'a>, flow::Transition)> {
|
pub(crate) fn noop_nothing(tag: Tag<'static>) -> Result<(Response<'static>, flow::Transition)> {
|
||||||
Ok((
|
Ok((
|
||||||
Response::build().tag(tag).message("Noop completed.").ok()?,
|
Response::build().tag(tag).message("Noop completed.").ok()?,
|
||||||
flow::Transition::None,
|
flow::Transition::None,
|
||||||
|
@ -41,7 +41,7 @@ pub(crate) fn not_implemented<'a>(
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn wrong_state<'a>(tag: Tag<'a>) -> Result<(Response<'a>, flow::Transition)> {
|
pub(crate) fn wrong_state(tag: Tag<'static>) -> Result<(Response<'static>, flow::Transition)> {
|
||||||
Ok((
|
Ok((
|
||||||
Response::build()
|
Response::build()
|
||||||
.tag(tag)
|
.tag(tag)
|
||||||
|
|
|
@ -21,18 +21,18 @@ use crate::mail::user::{User, MAILBOX_HIERARCHY_DELIMITER as MBX_HIER_DELIM_RAW}
|
||||||
use crate::mail::IMF;
|
use crate::mail::IMF;
|
||||||
|
|
||||||
pub struct AuthenticatedContext<'a> {
|
pub struct AuthenticatedContext<'a> {
|
||||||
pub req: &'a Command<'a>,
|
pub req: &'a Command<'static>,
|
||||||
pub user: &'a Arc<User>,
|
pub user: &'a Arc<User>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn dispatch<'a>(
|
pub async fn dispatch<'a>(
|
||||||
ctx: AuthenticatedContext<'a>,
|
ctx: AuthenticatedContext<'a>,
|
||||||
) -> Result<(Response<'a>, flow::Transition)> {
|
) -> Result<(Response<'static>, flow::Transition)> {
|
||||||
match &ctx.req.body {
|
match &ctx.req.body {
|
||||||
// Any state
|
// Any state
|
||||||
CommandBody::Noop => anystate::noop_nothing(ctx.req.tag.clone()),
|
CommandBody::Noop => anystate::noop_nothing(ctx.req.tag.clone()),
|
||||||
CommandBody::Capability => anystate::capability(ctx.req.tag.clone()),
|
CommandBody::Capability => anystate::capability(ctx.req.tag.clone()),
|
||||||
CommandBody::Logout => Ok((Response::bye()?, flow::Transition::Logout)),
|
CommandBody::Logout => anystate::logout(),
|
||||||
|
|
||||||
// Specific to this state (11 commands)
|
// Specific to this state (11 commands)
|
||||||
CommandBody::Create { mailbox } => ctx.create(mailbox).await,
|
CommandBody::Create { mailbox } => ctx.create(mailbox).await,
|
||||||
|
@ -68,7 +68,10 @@ pub async fn dispatch<'a>(
|
||||||
|
|
||||||
// --- PRIVATE ---
|
// --- PRIVATE ---
|
||||||
impl<'a> AuthenticatedContext<'a> {
|
impl<'a> AuthenticatedContext<'a> {
|
||||||
async fn create(self, mailbox: &MailboxCodec<'a>) -> Result<(Response<'a>, flow::Transition)> {
|
async fn create(
|
||||||
|
self,
|
||||||
|
mailbox: &MailboxCodec<'a>,
|
||||||
|
) -> Result<(Response<'static>, flow::Transition)> {
|
||||||
let name = match mailbox {
|
let name = match mailbox {
|
||||||
MailboxCodec::Inbox => {
|
MailboxCodec::Inbox => {
|
||||||
return Ok((
|
return Ok((
|
||||||
|
@ -100,7 +103,10 @@ impl<'a> AuthenticatedContext<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete(self, mailbox: &MailboxCodec<'a>) -> Result<(Response<'a>, flow::Transition)> {
|
async fn delete(
|
||||||
|
self,
|
||||||
|
mailbox: &MailboxCodec<'a>,
|
||||||
|
) -> Result<(Response<'static>, flow::Transition)> {
|
||||||
let name: &str = MailboxName(mailbox).try_into()?;
|
let name: &str = MailboxName(mailbox).try_into()?;
|
||||||
|
|
||||||
match self.user.delete_mailbox(&name).await {
|
match self.user.delete_mailbox(&name).await {
|
||||||
|
@ -125,7 +131,7 @@ impl<'a> AuthenticatedContext<'a> {
|
||||||
self,
|
self,
|
||||||
from: &MailboxCodec<'a>,
|
from: &MailboxCodec<'a>,
|
||||||
to: &MailboxCodec<'a>,
|
to: &MailboxCodec<'a>,
|
||||||
) -> Result<(Response<'a>, flow::Transition)> {
|
) -> Result<(Response<'static>, flow::Transition)> {
|
||||||
let name: &str = MailboxName(from).try_into()?;
|
let name: &str = MailboxName(from).try_into()?;
|
||||||
let new_name: &str = MailboxName(to).try_into()?;
|
let new_name: &str = MailboxName(to).try_into()?;
|
||||||
|
|
||||||
|
@ -152,7 +158,7 @@ impl<'a> AuthenticatedContext<'a> {
|
||||||
reference: &MailboxCodec<'a>,
|
reference: &MailboxCodec<'a>,
|
||||||
mailbox_wildcard: &ListMailbox<'a>,
|
mailbox_wildcard: &ListMailbox<'a>,
|
||||||
is_lsub: bool,
|
is_lsub: bool,
|
||||||
) -> Result<(Response<'a>, flow::Transition)> {
|
) -> Result<(Response<'static>, flow::Transition)> {
|
||||||
let mbx_hier_delim: QuotedChar = QuotedChar::unvalidated(MBX_HIER_DELIM_RAW);
|
let mbx_hier_delim: QuotedChar = QuotedChar::unvalidated(MBX_HIER_DELIM_RAW);
|
||||||
|
|
||||||
let reference: &str = MailboxName(reference).try_into()?;
|
let reference: &str = MailboxName(reference).try_into()?;
|
||||||
|
@ -259,9 +265,9 @@ impl<'a> AuthenticatedContext<'a> {
|
||||||
|
|
||||||
async fn status(
|
async fn status(
|
||||||
self,
|
self,
|
||||||
mailbox: &MailboxCodec<'a>,
|
mailbox: &MailboxCodec<'static>,
|
||||||
attributes: &[StatusDataItemName],
|
attributes: &[StatusDataItemName],
|
||||||
) -> Result<(Response<'a>, flow::Transition)> {
|
) -> Result<(Response<'static>, flow::Transition)> {
|
||||||
let name: &str = MailboxName(mailbox).try_into()?;
|
let name: &str = MailboxName(mailbox).try_into()?;
|
||||||
let mb_opt = self.user.open_mailbox(name).await?;
|
let mb_opt = self.user.open_mailbox(name).await?;
|
||||||
let mb = match mb_opt {
|
let mb = match mb_opt {
|
||||||
|
@ -316,7 +322,7 @@ impl<'a> AuthenticatedContext<'a> {
|
||||||
async fn subscribe(
|
async fn subscribe(
|
||||||
self,
|
self,
|
||||||
mailbox: &MailboxCodec<'a>,
|
mailbox: &MailboxCodec<'a>,
|
||||||
) -> Result<(Response<'a>, flow::Transition)> {
|
) -> Result<(Response<'static>, flow::Transition)> {
|
||||||
let name: &str = MailboxName(mailbox).try_into()?;
|
let name: &str = MailboxName(mailbox).try_into()?;
|
||||||
|
|
||||||
if self.user.has_mailbox(&name).await? {
|
if self.user.has_mailbox(&name).await? {
|
||||||
|
@ -341,7 +347,7 @@ impl<'a> AuthenticatedContext<'a> {
|
||||||
async fn unsubscribe(
|
async fn unsubscribe(
|
||||||
self,
|
self,
|
||||||
mailbox: &MailboxCodec<'a>,
|
mailbox: &MailboxCodec<'a>,
|
||||||
) -> Result<(Response<'a>, flow::Transition)> {
|
) -> Result<(Response<'static>, flow::Transition)> {
|
||||||
let name: &str = MailboxName(mailbox).try_into()?;
|
let name: &str = MailboxName(mailbox).try_into()?;
|
||||||
|
|
||||||
if self.user.has_mailbox(&name).await? {
|
if self.user.has_mailbox(&name).await? {
|
||||||
|
@ -399,7 +405,10 @@ impl<'a> AuthenticatedContext<'a> {
|
||||||
|
|
||||||
* TRACE END ---
|
* TRACE END ---
|
||||||
*/
|
*/
|
||||||
async fn select(self, mailbox: &MailboxCodec<'a>) -> Result<(Response<'a>, flow::Transition)> {
|
async fn select(
|
||||||
|
self,
|
||||||
|
mailbox: &MailboxCodec<'a>,
|
||||||
|
) -> Result<(Response<'static>, flow::Transition)> {
|
||||||
let name: &str = MailboxName(mailbox).try_into()?;
|
let name: &str = MailboxName(mailbox).try_into()?;
|
||||||
|
|
||||||
let mb_opt = self.user.open_mailbox(&name).await?;
|
let mb_opt = self.user.open_mailbox(&name).await?;
|
||||||
|
@ -430,7 +439,10 @@ impl<'a> AuthenticatedContext<'a> {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn examine(self, mailbox: &MailboxCodec<'a>) -> Result<(Response<'a>, flow::Transition)> {
|
async fn examine(
|
||||||
|
self,
|
||||||
|
mailbox: &MailboxCodec<'a>,
|
||||||
|
) -> Result<(Response<'static>, flow::Transition)> {
|
||||||
let name: &str = MailboxName(mailbox).try_into()?;
|
let name: &str = MailboxName(mailbox).try_into()?;
|
||||||
|
|
||||||
let mb_opt = self.user.open_mailbox(&name).await?;
|
let mb_opt = self.user.open_mailbox(&name).await?;
|
||||||
|
@ -468,7 +480,7 @@ impl<'a> AuthenticatedContext<'a> {
|
||||||
flags: &[Flag<'a>],
|
flags: &[Flag<'a>],
|
||||||
date: &Option<DateTime>,
|
date: &Option<DateTime>,
|
||||||
message: &Literal<'a>,
|
message: &Literal<'a>,
|
||||||
) -> Result<(Response<'a>, flow::Transition)> {
|
) -> Result<(Response<'static>, flow::Transition)> {
|
||||||
let append_tag = self.req.tag.clone();
|
let append_tag = self.req.tag.clone();
|
||||||
match self.append_internal(mailbox, flags, date, message).await {
|
match self.append_internal(mailbox, flags, date, message).await {
|
||||||
Ok((_mb, uidvalidity, uid)) => Ok((
|
Ok((_mb, uidvalidity, uid)) => Ok((
|
||||||
|
|
|
@ -14,17 +14,17 @@ use crate::imap::response::Response;
|
||||||
use crate::mail::user::User;
|
use crate::mail::user::User;
|
||||||
|
|
||||||
pub struct ExaminedContext<'a> {
|
pub struct ExaminedContext<'a> {
|
||||||
pub req: &'a Command<'a>,
|
pub req: &'a Command<'static>,
|
||||||
pub user: &'a Arc<User>,
|
pub user: &'a Arc<User>,
|
||||||
pub mailbox: &'a mut MailboxView,
|
pub mailbox: &'a mut MailboxView,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn dispatch<'a>(ctx: ExaminedContext<'a>) -> Result<(Response<'a>, flow::Transition)> {
|
pub async fn dispatch(ctx: ExaminedContext<'_>) -> Result<(Response<'static>, flow::Transition)> {
|
||||||
match &ctx.req.body {
|
match &ctx.req.body {
|
||||||
// Any State
|
// Any State
|
||||||
// noop is specific to this state
|
// noop is specific to this state
|
||||||
CommandBody::Capability => anystate::capability(ctx.req.tag.clone()),
|
CommandBody::Capability => anystate::capability(ctx.req.tag.clone()),
|
||||||
CommandBody::Logout => Ok((Response::bye()?, flow::Transition::Logout)),
|
CommandBody::Logout => anystate::logout(),
|
||||||
|
|
||||||
// Specific to the EXAMINE state (specialization of the SELECTED state)
|
// Specific to the EXAMINE state (specialization of the SELECTED state)
|
||||||
// ~3 commands -> close, fetch, search + NOOP
|
// ~3 commands -> close, fetch, search + NOOP
|
||||||
|
@ -58,7 +58,7 @@ pub async fn dispatch<'a>(ctx: ExaminedContext<'a>) -> Result<(Response<'a>, flo
|
||||||
impl<'a> ExaminedContext<'a> {
|
impl<'a> ExaminedContext<'a> {
|
||||||
/// CLOSE in examined state is not the same as in selected state
|
/// CLOSE in examined state is not the same as in selected state
|
||||||
/// (in selected state it also does an EXPUNGE, here it doesn't)
|
/// (in selected state it also does an EXPUNGE, here it doesn't)
|
||||||
async fn close(self) -> Result<(Response<'a>, flow::Transition)> {
|
async fn close(self) -> Result<(Response<'static>, flow::Transition)> {
|
||||||
Ok((
|
Ok((
|
||||||
Response::build()
|
Response::build()
|
||||||
.to_req(self.req)
|
.to_req(self.req)
|
||||||
|
@ -71,9 +71,9 @@ impl<'a> ExaminedContext<'a> {
|
||||||
pub async fn fetch(
|
pub async fn fetch(
|
||||||
self,
|
self,
|
||||||
sequence_set: &SequenceSet,
|
sequence_set: &SequenceSet,
|
||||||
attributes: &'a MacroOrMessageDataItemNames<'a>,
|
attributes: &'a MacroOrMessageDataItemNames<'static>,
|
||||||
uid: &bool,
|
uid: &bool,
|
||||||
) -> Result<(Response<'a>, flow::Transition)> {
|
) -> Result<(Response<'static>, flow::Transition)> {
|
||||||
match self.mailbox.fetch(sequence_set, attributes, uid).await {
|
match self.mailbox.fetch(sequence_set, attributes, uid).await {
|
||||||
Ok(resp) => Ok((
|
Ok(resp) => Ok((
|
||||||
Response::build()
|
Response::build()
|
||||||
|
@ -98,7 +98,7 @@ impl<'a> ExaminedContext<'a> {
|
||||||
_charset: &Option<Charset<'a>>,
|
_charset: &Option<Charset<'a>>,
|
||||||
_criteria: &SearchKey<'a>,
|
_criteria: &SearchKey<'a>,
|
||||||
_uid: &bool,
|
_uid: &bool,
|
||||||
) -> Result<(Response<'a>, flow::Transition)> {
|
) -> Result<(Response<'static>, flow::Transition)> {
|
||||||
Ok((
|
Ok((
|
||||||
Response::build()
|
Response::build()
|
||||||
.to_req(self.req)
|
.to_req(self.req)
|
||||||
|
@ -108,7 +108,7 @@ impl<'a> ExaminedContext<'a> {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn noop(self) -> Result<(Response<'a>, flow::Transition)> {
|
pub async fn noop(self) -> Result<(Response<'static>, flow::Transition)> {
|
||||||
self.mailbox.mailbox.force_sync().await?;
|
self.mailbox.mailbox.force_sync().await?;
|
||||||
|
|
||||||
let updates = self.mailbox.update().await?;
|
let updates = self.mailbox.update().await?;
|
||||||
|
|
|
@ -18,17 +18,19 @@ use crate::imap::response::Response;
|
||||||
use crate::mail::user::User;
|
use crate::mail::user::User;
|
||||||
|
|
||||||
pub struct SelectedContext<'a> {
|
pub struct SelectedContext<'a> {
|
||||||
pub req: &'a Command<'a>,
|
pub req: &'a Command<'static>,
|
||||||
pub user: &'a Arc<User>,
|
pub user: &'a Arc<User>,
|
||||||
pub mailbox: &'a mut MailboxView,
|
pub mailbox: &'a mut MailboxView,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn dispatch<'a>(ctx: SelectedContext<'a>) -> Result<(Response<'a>, flow::Transition)> {
|
pub async fn dispatch<'a>(
|
||||||
|
ctx: SelectedContext<'a>,
|
||||||
|
) -> Result<(Response<'static>, flow::Transition)> {
|
||||||
match &ctx.req.body {
|
match &ctx.req.body {
|
||||||
// Any State
|
// Any State
|
||||||
// noop is specific to this state
|
// noop is specific to this state
|
||||||
CommandBody::Capability => anystate::capability(ctx.req.tag.clone()),
|
CommandBody::Capability => anystate::capability(ctx.req.tag.clone()),
|
||||||
CommandBody::Logout => Ok((Response::bye()?, flow::Transition::Logout)),
|
CommandBody::Logout => anystate::logout(),
|
||||||
|
|
||||||
// Specific to this state (7 commands + NOOP)
|
// Specific to this state (7 commands + NOOP)
|
||||||
CommandBody::Close => ctx.close().await,
|
CommandBody::Close => ctx.close().await,
|
||||||
|
@ -65,7 +67,7 @@ pub async fn dispatch<'a>(ctx: SelectedContext<'a>) -> Result<(Response<'a>, flo
|
||||||
// --- PRIVATE ---
|
// --- PRIVATE ---
|
||||||
|
|
||||||
impl<'a> SelectedContext<'a> {
|
impl<'a> SelectedContext<'a> {
|
||||||
async fn close(self) -> Result<(Response<'a>, flow::Transition)> {
|
async fn close(self) -> Result<(Response<'static>, flow::Transition)> {
|
||||||
// We expunge messages,
|
// We expunge messages,
|
||||||
// but we don't send the untagged EXPUNGE responses
|
// but we don't send the untagged EXPUNGE responses
|
||||||
let tag = self.req.tag.clone();
|
let tag = self.req.tag.clone();
|
||||||
|
@ -79,9 +81,9 @@ impl<'a> SelectedContext<'a> {
|
||||||
pub async fn fetch(
|
pub async fn fetch(
|
||||||
self,
|
self,
|
||||||
sequence_set: &SequenceSet,
|
sequence_set: &SequenceSet,
|
||||||
attributes: &'a MacroOrMessageDataItemNames<'a>,
|
attributes: &'a MacroOrMessageDataItemNames<'static>,
|
||||||
uid: &bool,
|
uid: &bool,
|
||||||
) -> Result<(Response<'a>, flow::Transition)> {
|
) -> Result<(Response<'static>, flow::Transition)> {
|
||||||
match self.mailbox.fetch(sequence_set, attributes, uid).await {
|
match self.mailbox.fetch(sequence_set, attributes, uid).await {
|
||||||
Ok(resp) => Ok((
|
Ok(resp) => Ok((
|
||||||
Response::build()
|
Response::build()
|
||||||
|
@ -106,7 +108,7 @@ impl<'a> SelectedContext<'a> {
|
||||||
_charset: &Option<Charset<'a>>,
|
_charset: &Option<Charset<'a>>,
|
||||||
_criteria: &SearchKey<'a>,
|
_criteria: &SearchKey<'a>,
|
||||||
_uid: &bool,
|
_uid: &bool,
|
||||||
) -> Result<(Response<'a>, flow::Transition)> {
|
) -> Result<(Response<'static>, flow::Transition)> {
|
||||||
Ok((
|
Ok((
|
||||||
Response::build()
|
Response::build()
|
||||||
.to_req(self.req)
|
.to_req(self.req)
|
||||||
|
@ -116,7 +118,7 @@ impl<'a> SelectedContext<'a> {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn noop(self) -> Result<(Response<'a>, flow::Transition)> {
|
pub async fn noop(self) -> Result<(Response<'static>, flow::Transition)> {
|
||||||
self.mailbox.mailbox.force_sync().await?;
|
self.mailbox.mailbox.force_sync().await?;
|
||||||
|
|
||||||
let updates = self.mailbox.update().await?;
|
let updates = self.mailbox.update().await?;
|
||||||
|
@ -130,7 +132,7 @@ impl<'a> SelectedContext<'a> {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn expunge(self) -> Result<(Response<'a>, flow::Transition)> {
|
async fn expunge(self) -> Result<(Response<'static>, flow::Transition)> {
|
||||||
let tag = self.req.tag.clone();
|
let tag = self.req.tag.clone();
|
||||||
let data = self.mailbox.expunge().await?;
|
let data = self.mailbox.expunge().await?;
|
||||||
|
|
||||||
|
@ -151,7 +153,7 @@ impl<'a> SelectedContext<'a> {
|
||||||
response: &StoreResponse,
|
response: &StoreResponse,
|
||||||
flags: &[Flag<'a>],
|
flags: &[Flag<'a>],
|
||||||
uid: &bool,
|
uid: &bool,
|
||||||
) -> Result<(Response<'a>, flow::Transition)> {
|
) -> Result<(Response<'static>, flow::Transition)> {
|
||||||
let data = self
|
let data = self
|
||||||
.mailbox
|
.mailbox
|
||||||
.store(sequence_set, kind, response, flags, uid)
|
.store(sequence_set, kind, response, flags, uid)
|
||||||
|
@ -172,7 +174,7 @@ impl<'a> SelectedContext<'a> {
|
||||||
sequence_set: &SequenceSet,
|
sequence_set: &SequenceSet,
|
||||||
mailbox: &MailboxCodec<'a>,
|
mailbox: &MailboxCodec<'a>,
|
||||||
uid: &bool,
|
uid: &bool,
|
||||||
) -> Result<(Response<'a>, flow::Transition)> {
|
) -> Result<(Response<'static>, flow::Transition)> {
|
||||||
let name: &str = MailboxName(mailbox).try_into()?;
|
let name: &str = MailboxName(mailbox).try_into()?;
|
||||||
|
|
||||||
let mb_opt = self.user.open_mailbox(&name).await?;
|
let mb_opt = self.user.open_mailbox(&name).await?;
|
||||||
|
|
|
@ -37,23 +37,27 @@ pub enum Transition {
|
||||||
// See RFC3501 section 3.
|
// See RFC3501 section 3.
|
||||||
// https://datatracker.ietf.org/doc/html/rfc3501#page-13
|
// https://datatracker.ietf.org/doc/html/rfc3501#page-13
|
||||||
impl State {
|
impl State {
|
||||||
pub fn apply(self, tr: Transition) -> Result<Self, Error> {
|
pub fn apply(&mut self, tr: Transition) -> Result<(), Error> {
|
||||||
match (self, tr) {
|
let new_state = match (&self, tr) {
|
||||||
(s, Transition::None) => Ok(s),
|
(_s, Transition::None) => return Ok(()),
|
||||||
(State::NotAuthenticated, Transition::Authenticate(u)) => Ok(State::Authenticated(u)),
|
(State::NotAuthenticated, Transition::Authenticate(u)) => State::Authenticated(u),
|
||||||
(
|
(
|
||||||
State::Authenticated(u) | State::Selected(u, _) | State::Examined(u, _),
|
State::Authenticated(u) | State::Selected(u, _) | State::Examined(u, _),
|
||||||
Transition::Select(m),
|
Transition::Select(m),
|
||||||
) => Ok(State::Selected(u, m)),
|
) => State::Selected(u.clone(), m),
|
||||||
(
|
(
|
||||||
State::Authenticated(u) | State::Selected(u, _) | State::Examined(u, _),
|
State::Authenticated(u) | State::Selected(u, _) | State::Examined(u, _),
|
||||||
Transition::Examine(m),
|
Transition::Examine(m),
|
||||||
) => Ok(State::Examined(u, m)),
|
) => State::Examined(u.clone(), m),
|
||||||
(State::Selected(u, _) | State::Examined(u, _), Transition::Unselect) => {
|
(State::Selected(u, _) | State::Examined(u, _), Transition::Unselect) => {
|
||||||
Ok(State::Authenticated(u))
|
State::Authenticated(u.clone())
|
||||||
}
|
|
||||||
(_, Transition::Logout) => Ok(State::Logout),
|
|
||||||
_ => Err(Error::ForbiddenTransition),
|
|
||||||
}
|
}
|
||||||
|
(_, Transition::Logout) => State::Logout,
|
||||||
|
_ => return Err(Error::ForbiddenTransition),
|
||||||
|
};
|
||||||
|
|
||||||
|
*self = new_state;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,14 +75,26 @@ impl<'a> FetchedMail<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AttributesProxy<'a> {
|
pub struct AttributesProxy {
|
||||||
attrs: Vec<MessageDataItemName<'a>>,
|
attrs: Vec<MessageDataItemName<'static>>,
|
||||||
}
|
}
|
||||||
impl<'a> AttributesProxy<'a> {
|
impl AttributesProxy {
|
||||||
fn new(attrs: &'a MacroOrMessageDataItemNames<'a>, is_uid_fetch: bool) -> Self {
|
fn new(attrs: &MacroOrMessageDataItemNames<'static>, is_uid_fetch: bool) -> Self {
|
||||||
// Expand macros
|
// Expand macros
|
||||||
let mut fetch_attrs = match attrs {
|
let mut fetch_attrs = match attrs {
|
||||||
MacroOrMessageDataItemNames::Macro(m) => m.expand(),
|
MacroOrMessageDataItemNames::Macro(m) => {
|
||||||
|
use imap_codec::imap_types::fetch::Macro;
|
||||||
|
use MessageDataItemName::*;
|
||||||
|
match m {
|
||||||
|
Macro::All => vec![Flags, InternalDate, Rfc822Size, Envelope],
|
||||||
|
Macro::Fast => vec![Flags, InternalDate, Rfc822Size],
|
||||||
|
Macro::Full => vec![Flags, InternalDate, Rfc822Size, Envelope, Body],
|
||||||
|
_ => {
|
||||||
|
tracing::error!("unimplemented macro");
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
MacroOrMessageDataItemNames::MessageDataItemNames(a) => a.clone(),
|
MacroOrMessageDataItemNames::MessageDataItemNames(a) => a.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -248,7 +260,7 @@ impl<'a> MailView<'a> {
|
||||||
Ok(MessageDataItem::InternalDate(DateTime::unvalidated(dt)))
|
Ok(MessageDataItem::InternalDate(DateTime::unvalidated(dt)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn filter<'b>(&self, ap: &AttributesProxy<'b>) -> Result<(Body<'b>, SeenFlag)> {
|
fn filter<'b>(&self, ap: &AttributesProxy) -> Result<(Body<'static>, SeenFlag)> {
|
||||||
let mut seen = SeenFlag::DoNothing;
|
let mut seen = SeenFlag::DoNothing;
|
||||||
let res_attrs = ap
|
let res_attrs = ap
|
||||||
.attrs
|
.attrs
|
||||||
|
@ -593,9 +605,9 @@ impl MailboxView {
|
||||||
pub async fn fetch<'b>(
|
pub async fn fetch<'b>(
|
||||||
&self,
|
&self,
|
||||||
sequence_set: &SequenceSet,
|
sequence_set: &SequenceSet,
|
||||||
attributes: &'b MacroOrMessageDataItemNames<'b>,
|
attributes: &'b MacroOrMessageDataItemNames<'static>,
|
||||||
is_uid_fetch: &bool,
|
is_uid_fetch: &bool,
|
||||||
) -> Result<Vec<Body<'b>>> {
|
) -> Result<Vec<Body<'static>>> {
|
||||||
let ap = AttributesProxy::new(attributes, *is_uid_fetch);
|
let ap = AttributesProxy::new(attributes, *is_uid_fetch);
|
||||||
|
|
||||||
// Prepare data
|
// Prepare data
|
||||||
|
|
225
src/imap/mod.rs
225
src/imap/mod.rs
|
@ -4,104 +4,183 @@ mod mailbox_view;
|
||||||
mod response;
|
mod response;
|
||||||
mod session;
|
mod session;
|
||||||
|
|
||||||
use std::task::{Context, Poll};
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
//use boitalettres::errors::Error as BalError;
|
use futures::stream::{FuturesUnordered, StreamExt};
|
||||||
//use boitalettres::proto::{Request, Response};
|
|
||||||
//use boitalettres::server::accept::addr::AddrIncoming;
|
use tokio::net::TcpListener;
|
||||||
//use boitalettres::server::accept::addr::AddrStream;
|
|
||||||
//use boitalettres::server::Server as ImapServer;
|
|
||||||
use futures::future::BoxFuture;
|
|
||||||
use futures::future::FutureExt;
|
|
||||||
use tokio::sync::watch;
|
use tokio::sync::watch;
|
||||||
|
|
||||||
|
use imap_codec::imap_types::response::Greeting;
|
||||||
|
use imap_flow::server::{ServerFlow, ServerFlowEvent, ServerFlowOptions};
|
||||||
|
use imap_flow::stream::AnyStream;
|
||||||
|
|
||||||
use crate::config::ImapConfig;
|
use crate::config::ImapConfig;
|
||||||
use crate::login::ArcLoginProvider;
|
use crate::login::ArcLoginProvider;
|
||||||
|
|
||||||
/// Server is a thin wrapper to register our Services in BàL
|
/// Server is a thin wrapper to register our Services in BàL
|
||||||
pub struct Server {}
|
pub struct Server {
|
||||||
|
bind_addr: SocketAddr,
|
||||||
pub async fn new(config: ImapConfig, login: ArcLoginProvider) -> Result<Server> {
|
|
||||||
unimplemented!();
|
|
||||||
/* let incoming = AddrIncoming::new(config.bind_addr).await?;
|
|
||||||
tracing::info!("IMAP activated, will listen on {:#}", incoming.local_addr);
|
|
||||||
|
|
||||||
let imap = ImapServer::new(incoming).serve(Instance::new(login.clone()));
|
|
||||||
Ok(Server(imap))*/
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Server {
|
|
||||||
pub async fn run(self, mut must_exit: watch::Receiver<bool>) -> Result<()> {
|
|
||||||
tracing::info!("IMAP started!");
|
|
||||||
unimplemented!();
|
|
||||||
/*tokio::select! {
|
|
||||||
s = self.0 => s?,
|
|
||||||
_ = must_exit.changed() => tracing::info!("Stopped IMAP server"),
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//---
|
|
||||||
/*
|
|
||||||
/// Instance is the main Tokio Tower service that we register in BàL.
|
|
||||||
/// It receives new connection demands and spawn a dedicated service.
|
|
||||||
struct Instance {
|
|
||||||
login_provider: ArcLoginProvider,
|
login_provider: ArcLoginProvider,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Instance {
|
struct ClientContext {
|
||||||
pub fn new(login_provider: ArcLoginProvider) -> Self {
|
stream: AnyStream,
|
||||||
Self { login_provider }
|
addr: SocketAddr,
|
||||||
|
login_provider: ArcLoginProvider,
|
||||||
|
must_exit: watch::Receiver<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(config: ImapConfig, login: ArcLoginProvider) -> Server {
|
||||||
|
Server {
|
||||||
|
bind_addr: config.bind_addr,
|
||||||
|
login_provider: login,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Service<&'a AddrStream> for Instance {
|
impl Server {
|
||||||
type Response = Connection;
|
pub async fn run(self: Self, mut must_exit: watch::Receiver<bool>) -> Result<()> {
|
||||||
type Error = anyhow::Error;
|
let tcp = TcpListener::bind(self.bind_addr).await?;
|
||||||
type Future = BoxFuture<'static, Result<Self::Response>>;
|
tracing::info!("IMAP server listening on {:#}", self.bind_addr);
|
||||||
|
|
||||||
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
let mut connections = FuturesUnordered::new();
|
||||||
Poll::Ready(Ok(()))
|
|
||||||
|
while !*must_exit.borrow() {
|
||||||
|
let wait_conn_finished = async {
|
||||||
|
if connections.is_empty() {
|
||||||
|
futures::future::pending().await
|
||||||
|
} else {
|
||||||
|
connections.next().await
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
let (socket, remote_addr) = tokio::select! {
|
||||||
|
a = tcp.accept() => a?,
|
||||||
|
_ = wait_conn_finished => continue,
|
||||||
|
_ = must_exit.changed() => continue,
|
||||||
|
};
|
||||||
|
tracing::info!("IMAP: accepted connection from {}", remote_addr);
|
||||||
|
|
||||||
fn call(&mut self, addr: &'a AddrStream) -> Self::Future {
|
let client = ClientContext {
|
||||||
tracing::info!(remote_addr = %addr.remote_addr, local_addr = %addr.local_addr, "accept");
|
stream: AnyStream::new(socket),
|
||||||
let lp = self.login_provider.clone();
|
addr: remote_addr.clone(),
|
||||||
async { Ok(Connection::new(lp)) }.boxed()
|
login_provider: self.login_provider.clone(),
|
||||||
|
must_exit: must_exit.clone(),
|
||||||
|
};
|
||||||
|
let conn = tokio::spawn(client_wrapper(client));
|
||||||
|
connections.push(conn);
|
||||||
|
}
|
||||||
|
drop(tcp);
|
||||||
|
|
||||||
|
tracing::info!("IMAP server shutting down, draining remaining connections...");
|
||||||
|
while connections.next().await.is_some() {}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//---
|
async fn client_wrapper(ctx: ClientContext) {
|
||||||
|
let addr = ctx.addr.clone();
|
||||||
/// Connection is the per-connection Tokio Tower service we register in BàL.
|
match client(ctx).await {
|
||||||
/// It handles a single TCP connection, and thus has a business logic.
|
Ok(()) => {
|
||||||
struct Connection {
|
tracing::info!("closing successful session for {:?}", addr);
|
||||||
session: session::Manager,
|
}
|
||||||
}
|
Err(e) => {
|
||||||
|
tracing::error!("closing errored session for {:?}: {}", addr, e);
|
||||||
impl Connection {
|
|
||||||
pub fn new(login_provider: ArcLoginProvider) -> Self {
|
|
||||||
Self {
|
|
||||||
session: session::Manager::new(login_provider),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Service<Request> for Connection {
|
async fn client(mut ctx: ClientContext) -> Result<()> {
|
||||||
type Response = Response;
|
// Send greeting
|
||||||
type Error = BalError;
|
let (mut server, _) = ServerFlow::send_greeting(
|
||||||
type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
|
ctx.stream,
|
||||||
|
ServerFlowOptions::default(),
|
||||||
|
Greeting::ok(None, "Aerogramme").unwrap(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
use crate::imap::response::{Body, Response as MyResponse};
|
||||||
Poll::Ready(Ok(()))
|
use crate::imap::session::Instance;
|
||||||
|
use imap_codec::imap_types::command::Command;
|
||||||
|
use imap_codec::imap_types::response::{Response, Status};
|
||||||
|
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
let (cmd_tx, mut cmd_rx) = mpsc::channel::<Command<'static>>(10);
|
||||||
|
let (resp_tx, mut resp_rx) = mpsc::unbounded_channel::<MyResponse<'static>>();
|
||||||
|
|
||||||
|
let bckgrnd = tokio::spawn(async move {
|
||||||
|
let mut session = Instance::new(ctx.login_provider);
|
||||||
|
loop {
|
||||||
|
let cmd = match cmd_rx.recv().await {
|
||||||
|
None => break,
|
||||||
|
Some(cmd_recv) => cmd_recv,
|
||||||
|
};
|
||||||
|
|
||||||
|
let maybe_response = session.command(cmd).await;
|
||||||
|
|
||||||
|
match resp_tx.send(maybe_response) {
|
||||||
|
Err(_) => break,
|
||||||
|
Ok(_) => (),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
tracing::info!("runner is quitting");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Main loop
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
// Managing imap_flow stuff
|
||||||
|
srv_evt = server.progress() => match srv_evt? {
|
||||||
|
ServerFlowEvent::ResponseSent { handle: _handle, response } => {
|
||||||
|
match response {
|
||||||
|
Response::Status(Status::Bye(_)) => break,
|
||||||
|
_ => tracing::trace!("sent to {} content {:?}", ctx.addr, response),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ServerFlowEvent::CommandReceived { command } => {
|
||||||
|
match cmd_tx.try_send(command) {
|
||||||
|
Ok(_) => (),
|
||||||
|
Err(mpsc::error::TrySendError::Full(_)) => {
|
||||||
|
server.enqueue_status(Status::bye(None, "Too fast").unwrap());
|
||||||
|
tracing::error!("client {:?} is sending commands too fast, closing.", ctx.addr);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
server.enqueue_status(Status::bye(None, "Internal session exited").unwrap());
|
||||||
|
tracing::error!("session task exited for {:?}, quitting", ctx.addr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Managing response generated by Aerogramme
|
||||||
|
maybe_msg = resp_rx.recv() => {
|
||||||
|
let response = match maybe_msg {
|
||||||
|
None => {
|
||||||
|
server.enqueue_status(Status::bye(None, "Internal session exited").unwrap());
|
||||||
|
tracing::error!("session task exited for {:?}, quitting", ctx.addr);
|
||||||
|
continue
|
||||||
|
},
|
||||||
|
Some(r) => r,
|
||||||
|
};
|
||||||
|
|
||||||
|
for body_elem in response.body.into_iter() {
|
||||||
|
let _handle = match body_elem {
|
||||||
|
Body::Data(d) => server.enqueue_data(d),
|
||||||
|
Body::Status(s) => server.enqueue_status(s),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
server.enqueue_status(response.completion);
|
||||||
|
},
|
||||||
|
|
||||||
|
// When receiving a CTRL+C
|
||||||
|
_ = ctx.must_exit.changed() => {
|
||||||
|
server.enqueue_status(Status::bye(None, "Server is being shutdown").unwrap());
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call(&mut self, req: Request) -> Self::Future {
|
drop(cmd_tx);
|
||||||
tracing::debug!("Got request: {:#?}", req.command);
|
bckgrnd.await?;
|
||||||
self.session.process(req)
|
Ok(())
|
||||||
}
|
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
|
@ -47,11 +47,13 @@ impl<'a> ResponseBuilder<'a> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn info(mut self, status: Status<'a>) -> Self {
|
pub fn info(mut self, status: Status<'a>) -> Self {
|
||||||
self.body.push(Body::Status(status));
|
self.body.push(Body::Status(status));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn many_info(mut self, status: Vec<Status<'a>>) -> Self {
|
pub fn many_info(mut self, status: Vec<Status<'a>>) -> Self {
|
||||||
for d in status.into_iter() {
|
for d in status.into_iter() {
|
||||||
self = self.info(d);
|
self = self.info(d);
|
||||||
|
@ -87,8 +89,8 @@ impl<'a> ResponseBuilder<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Response<'a> {
|
pub struct Response<'a> {
|
||||||
body: Vec<Body<'a>>,
|
pub body: Vec<Body<'a>>,
|
||||||
completion: Status<'a>,
|
pub completion: Status<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Response<'a> {
|
impl<'a> Response<'a> {
|
||||||
|
|
|
@ -1,122 +1,40 @@
|
||||||
use anyhow::Error;
|
|
||||||
//use boitalettres::errors::Error as BalError;
|
|
||||||
//use boitalettres::proto::{Request, Response};
|
|
||||||
use futures::future::BoxFuture;
|
|
||||||
use futures::future::FutureExt;
|
|
||||||
|
|
||||||
use tokio::sync::mpsc::error::TrySendError;
|
|
||||||
use tokio::sync::{mpsc, oneshot};
|
|
||||||
|
|
||||||
use crate::imap::command::{anonymous, authenticated, examined, selected};
|
use crate::imap::command::{anonymous, authenticated, examined, selected};
|
||||||
use crate::imap::flow;
|
use crate::imap::flow;
|
||||||
|
use crate::imap::response::Response;
|
||||||
use crate::login::ArcLoginProvider;
|
use crate::login::ArcLoginProvider;
|
||||||
|
use imap_codec::imap_types::command::Command;
|
||||||
/*
|
|
||||||
/* This constant configures backpressure in the system,
|
|
||||||
* or more specifically, how many pipelined messages are allowed
|
|
||||||
* before refusing them
|
|
||||||
*/
|
|
||||||
const MAX_PIPELINED_COMMANDS: usize = 10;
|
|
||||||
|
|
||||||
struct Message {
|
|
||||||
req: Request,
|
|
||||||
tx: oneshot::Sender<Result<Response, BalError>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
//-----
|
//-----
|
||||||
|
|
||||||
pub struct Manager {
|
|
||||||
tx: mpsc::Sender<Message>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Manager {
|
|
||||||
pub fn new(login_provider: ArcLoginProvider) -> Self {
|
|
||||||
let (tx, rx) = mpsc::channel(MAX_PIPELINED_COMMANDS);
|
|
||||||
tokio::spawn(async move {
|
|
||||||
let instance = Instance::new(login_provider, rx);
|
|
||||||
instance.start().await;
|
|
||||||
});
|
|
||||||
Self { tx }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn process(&self, req: Request) -> BoxFuture<'static, Result<Response, BalError>> {
|
|
||||||
let (tx, rx) = oneshot::channel();
|
|
||||||
let msg = Message { req, tx };
|
|
||||||
|
|
||||||
// We use try_send on a bounded channel to protect the daemons from DoS.
|
|
||||||
// Pipelining requests in IMAP are a special case: they should not occure often
|
|
||||||
// and in a limited number (like 3 requests). Someone filling the channel
|
|
||||||
// will probably be malicious so we "rate limit" them.
|
|
||||||
match self.tx.try_send(msg) {
|
|
||||||
Ok(()) => (),
|
|
||||||
Err(TrySendError::Full(_)) => {
|
|
||||||
return async { Response::bad("Too fast! Send less pipelined requests.") }.boxed()
|
|
||||||
}
|
|
||||||
Err(TrySendError::Closed(_)) => {
|
|
||||||
return async { Err(BalError::Text("Terminated session".to_string())) }.boxed()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// @FIXME add a timeout, handle a session that fails.
|
|
||||||
async {
|
|
||||||
match rx.await {
|
|
||||||
Ok(r) => r,
|
|
||||||
Err(e) => {
|
|
||||||
tracing::warn!("Got error {:#?}", e);
|
|
||||||
Response::bad("No response from the session handler")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.boxed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
//-----
|
|
||||||
/*
|
|
||||||
pub struct Instance {
|
pub struct Instance {
|
||||||
rx: mpsc::Receiver<Message>,
|
|
||||||
|
|
||||||
pub login_provider: ArcLoginProvider,
|
pub login_provider: ArcLoginProvider,
|
||||||
pub state: flow::State,
|
pub state: flow::State,
|
||||||
}
|
}
|
||||||
impl Instance {
|
impl Instance {
|
||||||
fn new(login_provider: ArcLoginProvider, rx: mpsc::Receiver<Message>) -> Self {
|
pub fn new(login_provider: ArcLoginProvider) -> Self {
|
||||||
Self {
|
Self {
|
||||||
login_provider,
|
login_provider,
|
||||||
rx,
|
|
||||||
state: flow::State::NotAuthenticated,
|
state: flow::State::NotAuthenticated,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//@FIXME add a function that compute the runner's name from its local info
|
pub async fn command(&mut self, cmd: Command<'static>) -> Response<'static> {
|
||||||
// to ease debug
|
|
||||||
// fn name(&self) -> String { }
|
|
||||||
|
|
||||||
async fn start(mut self) {
|
|
||||||
//@FIXME add more info about the runner
|
|
||||||
tracing::debug!("starting runner");
|
|
||||||
|
|
||||||
while let Some(msg) = self.rx.recv().await {
|
|
||||||
// Command behavior is modulated by the state.
|
// Command behavior is modulated by the state.
|
||||||
// To prevent state error, we handle the same command in separate code paths.
|
// To prevent state error, we handle the same command in separate code paths.
|
||||||
let ctrl = match &mut self.state {
|
let (resp, tr) = match &mut self.state {
|
||||||
flow::State::NotAuthenticated => {
|
flow::State::NotAuthenticated => {
|
||||||
let ctx = anonymous::AnonymousContext {
|
let ctx = anonymous::AnonymousContext {
|
||||||
req: &msg.req,
|
req: &cmd,
|
||||||
login_provider: Some(&self.login_provider),
|
login_provider: &self.login_provider,
|
||||||
};
|
};
|
||||||
anonymous::dispatch(ctx).await
|
anonymous::dispatch(ctx).await
|
||||||
}
|
}
|
||||||
flow::State::Authenticated(ref user) => {
|
flow::State::Authenticated(ref user) => {
|
||||||
let ctx = authenticated::AuthenticatedContext {
|
let ctx = authenticated::AuthenticatedContext { req: &cmd, user };
|
||||||
req: &msg.req,
|
|
||||||
user,
|
|
||||||
};
|
|
||||||
authenticated::dispatch(ctx).await
|
authenticated::dispatch(ctx).await
|
||||||
}
|
}
|
||||||
flow::State::Selected(ref user, ref mut mailbox) => {
|
flow::State::Selected(ref user, ref mut mailbox) => {
|
||||||
let ctx = selected::SelectedContext {
|
let ctx = selected::SelectedContext {
|
||||||
req: &msg.req,
|
req: &cmd,
|
||||||
user,
|
user,
|
||||||
mailbox,
|
mailbox,
|
||||||
};
|
};
|
||||||
|
@ -124,59 +42,45 @@ impl Instance {
|
||||||
}
|
}
|
||||||
flow::State::Examined(ref user, ref mut mailbox) => {
|
flow::State::Examined(ref user, ref mut mailbox) => {
|
||||||
let ctx = examined::ExaminedContext {
|
let ctx = examined::ExaminedContext {
|
||||||
req: &msg.req,
|
req: &cmd,
|
||||||
user,
|
user,
|
||||||
mailbox,
|
mailbox,
|
||||||
};
|
};
|
||||||
examined::dispatch(ctx).await
|
examined::dispatch(ctx).await
|
||||||
}
|
}
|
||||||
flow::State::Logout => {
|
flow::State::Logout => Response::build()
|
||||||
Response::bad("No commands are allowed in the LOGOUT state.")
|
.tag(cmd.tag.clone())
|
||||||
.map(|r| (r, flow::Transition::None))
|
.message("No commands are allowed in the LOGOUT state.")
|
||||||
.map_err(Error::msg)
|
.bad()
|
||||||
|
.map(|r| (r, flow::Transition::None)),
|
||||||
}
|
}
|
||||||
};
|
.unwrap_or_else(|err| {
|
||||||
|
tracing::error!("Command error {:?} occured while processing {:?}", err, cmd);
|
||||||
// Process result
|
(
|
||||||
let res = match ctrl {
|
Response::build()
|
||||||
Ok((res, tr)) => {
|
.to_req(&cmd)
|
||||||
//@FIXME remove unwrap
|
.message("Internal error while processing command")
|
||||||
self.state = match self.state.apply(tr) {
|
.bad()
|
||||||
Ok(new_state) => new_state,
|
.unwrap(),
|
||||||
Err(e) => {
|
flow::Transition::None,
|
||||||
tracing::error!("Invalid transition: {}, exiting", e);
|
)
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//@FIXME enrich here the command with some global status
|
|
||||||
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
// Cast from anyhow::Error to Bal::Error
|
|
||||||
// @FIXME proper error handling would be great
|
|
||||||
Err(e) => match e.downcast::<BalError>() {
|
|
||||||
Ok(be) => Err(be),
|
|
||||||
Err(e) => {
|
|
||||||
tracing::warn!(error=%e, "internal.error");
|
|
||||||
Response::bad("Internal error")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
//@FIXME I think we should quit this thread on error and having our manager watch it,
|
|
||||||
// and then abort the session as it is corrupted.
|
|
||||||
msg.tx.send(res).unwrap_or_else(|e| {
|
|
||||||
tracing::warn!("failed to send imap response to manager: {:#?}", e)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if let flow::State::Logout = &self.state {
|
if let Err(e) = self.state.apply(tr) {
|
||||||
break;
|
tracing::error!(
|
||||||
}
|
"Transition error {:?} occured while processing on command {:?}",
|
||||||
|
e,
|
||||||
|
cmd
|
||||||
|
);
|
||||||
|
return Response::build()
|
||||||
|
.to_req(&cmd)
|
||||||
|
.message(
|
||||||
|
"Internal error, processing command triggered an illegal IMAP state transition",
|
||||||
|
)
|
||||||
|
.bad()
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
//@FIXME add more info about the runner
|
resp
|
||||||
tracing::debug!("exiting runner");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ impl Server {
|
||||||
let login = Arc::new(StaticLoginProvider::new(config.users).await?);
|
let login = Arc::new(StaticLoginProvider::new(config.users).await?);
|
||||||
|
|
||||||
let lmtp_server = None;
|
let lmtp_server = None;
|
||||||
let imap_server = Some(imap::new(config.imap, login.clone()).await?);
|
let imap_server = Some(imap::new(config.imap, login.clone()));
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
lmtp_server,
|
lmtp_server,
|
||||||
imap_server,
|
imap_server,
|
||||||
|
@ -42,7 +42,7 @@ impl Server {
|
||||||
};
|
};
|
||||||
|
|
||||||
let lmtp_server = Some(LmtpServer::new(config.lmtp, login.clone()));
|
let lmtp_server = Some(LmtpServer::new(config.lmtp, login.clone()));
|
||||||
let imap_server = Some(imap::new(config.imap, login.clone()).await?);
|
let imap_server = Some(imap::new(config.imap, login.clone()));
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
lmtp_server,
|
lmtp_server,
|
||||||
|
|
Loading…
Reference in a new issue