Switching to upstream boitalettres
This commit is contained in:
parent
390bad0ec4
commit
927b461f25
7 changed files with 40 additions and 102 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -362,7 +362,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "boitalettres"
|
name = "boitalettres"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
source = "git+https://git.deuxfleurs.fr/quentin/boitalettres.git?branch=expose_req_res#2c43b7686a7cd06f733719350bd61f792d20338e"
|
source = "git+https://git.deuxfleurs.fr/KokaKiwi/boitalettres.git?branch=main#01ee8c872b15e2aecb4c0ef5c2dd3aa6cc3c4d3c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-compat",
|
"async-compat",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
|
|
@ -41,7 +41,7 @@ tower = "0.4"
|
||||||
imap-codec = "0.5"
|
imap-codec = "0.5"
|
||||||
|
|
||||||
k2v-client = { git = "https://git.deuxfleurs.fr/Deuxfleurs/garage.git", branch = "main" }
|
k2v-client = { git = "https://git.deuxfleurs.fr/Deuxfleurs/garage.git", branch = "main" }
|
||||||
boitalettres = { git = "https://git.deuxfleurs.fr/quentin/boitalettres.git", branch = "expose_req_res" }
|
boitalettres = { git = "https://git.deuxfleurs.fr/KokaKiwi/boitalettres.git", branch = "main" }
|
||||||
smtp-message = { git = "http://github.com/Alexis211/kannader", branch = "feature/lmtp" }
|
smtp-message = { git = "http://github.com/Alexis211/kannader", branch = "feature/lmtp" }
|
||||||
smtp-server = { git = "http://github.com/Alexis211/kannader", branch = "feature/lmtp" }
|
smtp-server = { git = "http://github.com/Alexis211/kannader", branch = "feature/lmtp" }
|
||||||
|
|
||||||
|
|
|
@ -10,16 +10,13 @@ use crate::imap::session::InnerContext;
|
||||||
//--- dispatching
|
//--- dispatching
|
||||||
|
|
||||||
pub async fn dispatch<'a>(ctx: InnerContext<'a>) -> Result<(Response, flow::Transition)> {
|
pub async fn dispatch<'a>(ctx: InnerContext<'a>) -> Result<(Response, flow::Transition)> {
|
||||||
match &ctx.req.body {
|
match &ctx.req.command.body {
|
||||||
CommandBody::Capability => capability(ctx).await,
|
CommandBody::Capability => capability(ctx).await,
|
||||||
CommandBody::Login { username, password } => login(ctx, username, password).await,
|
CommandBody::Login { username, password } => login(ctx, username, password).await,
|
||||||
_ => Status::no(
|
_ => Ok((
|
||||||
Some(ctx.req.tag.clone()),
|
Response::no("This command is not available in the ANONYMOUS state.")?,
|
||||||
None,
|
flow::Transition::No
|
||||||
"This command is not available in the ANONYMOUS state.",
|
)),
|
||||||
)
|
|
||||||
.map(|s| (vec![ImapRes::Status(s)], flow::Transition::No))
|
|
||||||
.map_err(Error::msg),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,13 +24,7 @@ pub async fn dispatch<'a>(ctx: InnerContext<'a>) -> Result<(Response, flow::Tran
|
||||||
|
|
||||||
async fn capability<'a>(ctx: InnerContext<'a>) -> Result<(Response, flow::Transition)> {
|
async fn capability<'a>(ctx: InnerContext<'a>) -> Result<(Response, flow::Transition)> {
|
||||||
let capabilities = vec![Capability::Imap4Rev1, Capability::Idle];
|
let capabilities = vec![Capability::Imap4Rev1, Capability::Idle];
|
||||||
let res = vec![
|
let res = Response::ok("Server capabilities")?.with_body(Data::Capability(capabilities));
|
||||||
ImapRes::Data(Data::Capability(capabilities)),
|
|
||||||
ImapRes::Status(
|
|
||||||
Status::ok(Some(ctx.req.tag.clone()), None, "Server capabilities")
|
|
||||||
.map_err(Error::msg)?,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
Ok((res, flow::Transition::No))
|
Ok((res, flow::Transition::No))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,13 +42,7 @@ async fn login<'a>(
|
||||||
let creds = match ctx.login.login(&u, &p).await {
|
let creds = match ctx.login.login(&u, &p).await {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::debug!(error=%e, "authentication failed");
|
tracing::debug!(error=%e, "authentication failed");
|
||||||
return Ok((
|
return Ok((Response::no("Authentication failed")?, flow::Transition::No));
|
||||||
vec![ImapRes::Status(
|
|
||||||
Status::no(Some(ctx.req.tag.clone()), None, "Authentication failed")
|
|
||||||
.map_err(Error::msg)?,
|
|
||||||
)],
|
|
||||||
flow::Transition::No,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
};
|
};
|
||||||
|
@ -66,16 +51,10 @@ async fn login<'a>(
|
||||||
creds,
|
creds,
|
||||||
name: u.clone(),
|
name: u.clone(),
|
||||||
};
|
};
|
||||||
let tr = flow::Transition::Authenticate(user);
|
|
||||||
|
|
||||||
tracing::info!(username=%u, "connected");
|
tracing::info!(username=%u, "connected");
|
||||||
Ok((
|
Ok((
|
||||||
vec![
|
Response::ok("Completed")?,
|
||||||
//@FIXME we could send a capability status here too
|
flow::Transition::Authenticate(user)
|
||||||
ImapRes::Status(
|
|
||||||
Status::ok(Some(ctx.req.tag.clone()), None, "completed").map_err(Error::msg)?,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
tr,
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use anyhow::{anyhow, Error, Result};
|
use anyhow::{anyhow, Error, Result};
|
||||||
use boitalettres::proto::Response;
|
use boitalettres::proto::{Response, res::body::Data as Body};
|
||||||
use imap_codec::types::command::CommandBody;
|
use imap_codec::types::command::CommandBody;
|
||||||
use imap_codec::types::core::{Atom, Tag};
|
use imap_codec::types::core::Atom;
|
||||||
use imap_codec::types::flag::Flag;
|
use imap_codec::types::flag::Flag;
|
||||||
use imap_codec::types::mailbox::{ListMailbox, Mailbox as MailboxCodec};
|
use imap_codec::types::mailbox::{ListMailbox, Mailbox as MailboxCodec};
|
||||||
use imap_codec::types::response::{Code, Data, Response as ImapRes, Status};
|
use imap_codec::types::response::{Code, Data, Response as ImapRes, Status};
|
||||||
|
@ -23,13 +23,9 @@ pub async fn dispatch<'a>(
|
||||||
inner: InnerContext<'a>,
|
inner: InnerContext<'a>,
|
||||||
user: &'a flow::User,
|
user: &'a flow::User,
|
||||||
) -> Result<(Response, flow::Transition)> {
|
) -> Result<(Response, flow::Transition)> {
|
||||||
let ctx = StateContext {
|
let ctx = StateContext { user, inner };
|
||||||
user,
|
|
||||||
tag: &inner.req.tag,
|
|
||||||
inner,
|
|
||||||
};
|
|
||||||
|
|
||||||
match &ctx.inner.req.body {
|
match &ctx.inner.req.command.body {
|
||||||
CommandBody::Lsub {
|
CommandBody::Lsub {
|
||||||
reference,
|
reference,
|
||||||
mailbox_wildcard,
|
mailbox_wildcard,
|
||||||
|
@ -48,7 +44,6 @@ pub async fn dispatch<'a>(
|
||||||
struct StateContext<'a> {
|
struct StateContext<'a> {
|
||||||
inner: InnerContext<'a>,
|
inner: InnerContext<'a>,
|
||||||
user: &'a flow::User,
|
user: &'a flow::User,
|
||||||
tag: &'a Tag,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> StateContext<'a> {
|
impl<'a> StateContext<'a> {
|
||||||
|
@ -58,9 +53,7 @@ impl<'a> StateContext<'a> {
|
||||||
mailbox_wildcard: &ListMailbox,
|
mailbox_wildcard: &ListMailbox,
|
||||||
) -> Result<(Response, flow::Transition)> {
|
) -> Result<(Response, flow::Transition)> {
|
||||||
Ok((
|
Ok((
|
||||||
vec![ImapRes::Status(
|
Response::bad("Not implemented")?,
|
||||||
Status::bad(Some(self.tag.clone()), None, "Not implemented").map_err(Error::msg)?,
|
|
||||||
)],
|
|
||||||
flow::Transition::No,
|
flow::Transition::No,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -71,9 +64,7 @@ impl<'a> StateContext<'a> {
|
||||||
mailbox_wildcard: &ListMailbox,
|
mailbox_wildcard: &ListMailbox,
|
||||||
) -> Result<(Response, flow::Transition)> {
|
) -> Result<(Response, flow::Transition)> {
|
||||||
Ok((
|
Ok((
|
||||||
vec![ImapRes::Status(
|
Response::bad("Not implemented")?,
|
||||||
Status::bad(Some(self.tag.clone()), None, "Not implemented").map_err(Error::msg)?,
|
|
||||||
)],
|
|
||||||
flow::Transition::No,
|
flow::Transition::No,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
@ -115,20 +106,11 @@ impl<'a> StateContext<'a> {
|
||||||
let sum = mb.summary().await?;
|
let sum = mb.summary().await?;
|
||||||
tracing::trace!(summary=%sum, "mailbox.summary");
|
tracing::trace!(summary=%sum, "mailbox.summary");
|
||||||
|
|
||||||
let r_unseen = Status::ok(
|
let mut res = Vec::<Body>::new();
|
||||||
None,
|
|
||||||
Some(Code::Unseen(
|
|
||||||
std::num::NonZeroU32::new(1).ok_or(anyhow!("Invalid message identifier"))?,
|
|
||||||
)),
|
|
||||||
"First unseen UID",
|
|
||||||
)
|
|
||||||
.map_err(Error::msg)?;
|
|
||||||
|
|
||||||
let mut res = Vec::<ImapRes>::new();
|
res.push(Body::Data(Data::Exists(sum.exists)));
|
||||||
|
|
||||||
res.push(ImapRes::Data(Data::Exists(sum.exists)));
|
res.push(Body::Data(Data::Recent(sum.recent)));
|
||||||
|
|
||||||
res.push(ImapRes::Data(Data::Recent(sum.recent)));
|
|
||||||
|
|
||||||
let mut flags: Vec<Flag> = sum.flags.map(|f| match f.chars().next() {
|
let mut flags: Vec<Flag> = sum.flags.map(|f| match f.chars().next() {
|
||||||
Some('\\') => None,
|
Some('\\') => None,
|
||||||
|
@ -144,7 +126,7 @@ impl<'a> StateContext<'a> {
|
||||||
}).flatten().collect();
|
}).flatten().collect();
|
||||||
flags.extend_from_slice(&DEFAULT_FLAGS);
|
flags.extend_from_slice(&DEFAULT_FLAGS);
|
||||||
|
|
||||||
res.push(ImapRes::Data(Data::Flags(flags.clone())));
|
res.push(Body::Data(Data::Flags(flags.clone())));
|
||||||
|
|
||||||
let uid_validity = Status::ok(
|
let uid_validity = Status::ok(
|
||||||
None,
|
None,
|
||||||
|
@ -152,14 +134,14 @@ impl<'a> StateContext<'a> {
|
||||||
"UIDs valid"
|
"UIDs valid"
|
||||||
)
|
)
|
||||||
.map_err(Error::msg)?;
|
.map_err(Error::msg)?;
|
||||||
res.push(ImapRes::Status(uid_validity));
|
res.push(Body::Status(uid_validity));
|
||||||
|
|
||||||
let next_uid = Status::ok(
|
let next_uid = Status::ok(
|
||||||
None,
|
None,
|
||||||
Some(Code::UidNext(sum.next)),
|
Some(Code::UidNext(sum.next)),
|
||||||
"Predict next UID"
|
"Predict next UID"
|
||||||
).map_err(Error::msg)?;
|
).map_err(Error::msg)?;
|
||||||
res.push(ImapRes::Status(next_uid));
|
res.push(Body::Status(next_uid));
|
||||||
|
|
||||||
if let Some(unseen) = sum.unseen {
|
if let Some(unseen) = sum.unseen {
|
||||||
let status_unseen = Status::ok(
|
let status_unseen = Status::ok(
|
||||||
|
@ -168,7 +150,7 @@ impl<'a> StateContext<'a> {
|
||||||
"First unseen UID",
|
"First unseen UID",
|
||||||
)
|
)
|
||||||
.map_err(Error::msg)?;
|
.map_err(Error::msg)?;
|
||||||
res.push(ImapRes::Status(status_unseen));
|
res.push(Body::Status(status_unseen));
|
||||||
}
|
}
|
||||||
|
|
||||||
flags.push(Flag::Permanent);
|
flags.push(Flag::Permanent);
|
||||||
|
@ -177,16 +159,11 @@ impl<'a> StateContext<'a> {
|
||||||
Some(Code::PermanentFlags(flags)),
|
Some(Code::PermanentFlags(flags)),
|
||||||
"Flags permitted",
|
"Flags permitted",
|
||||||
).map_err(Error::msg)?;
|
).map_err(Error::msg)?;
|
||||||
res.push(ImapRes::Status(permanent_flags));
|
res.push(Body::Status(permanent_flags));
|
||||||
|
|
||||||
let last = Status::ok(
|
Ok((
|
||||||
Some(self.tag.clone()),
|
Response::ok("Select completed")?.with_body(res),
|
||||||
Some(Code::ReadWrite),
|
flow::Transition::Select(mb),
|
||||||
"Select completed",
|
))
|
||||||
).map_err(Error::msg)?;
|
|
||||||
res.push(ImapRes::Status(last));
|
|
||||||
|
|
||||||
let tr = flow::Transition::Select(mb);
|
|
||||||
Ok((res, tr))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,13 +17,12 @@ pub async fn dispatch<'a>(
|
||||||
mailbox: &'a Mailbox,
|
mailbox: &'a Mailbox,
|
||||||
) -> Result<(Response, flow::Transition)> {
|
) -> Result<(Response, flow::Transition)> {
|
||||||
let ctx = StateContext {
|
let ctx = StateContext {
|
||||||
tag: &inner.req.tag,
|
|
||||||
inner,
|
inner,
|
||||||
user,
|
user,
|
||||||
mailbox,
|
mailbox,
|
||||||
};
|
};
|
||||||
|
|
||||||
match &ctx.inner.req.body {
|
match &ctx.inner.req.command.body {
|
||||||
CommandBody::Fetch {
|
CommandBody::Fetch {
|
||||||
sequence_set,
|
sequence_set,
|
||||||
attributes,
|
attributes,
|
||||||
|
@ -39,7 +38,6 @@ struct StateContext<'a> {
|
||||||
inner: InnerContext<'a>,
|
inner: InnerContext<'a>,
|
||||||
user: &'a flow::User,
|
user: &'a flow::User,
|
||||||
mailbox: &'a Mailbox,
|
mailbox: &'a Mailbox,
|
||||||
tag: &'a Tag,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> StateContext<'a> {
|
impl<'a> StateContext<'a> {
|
||||||
|
@ -50,9 +48,7 @@ impl<'a> StateContext<'a> {
|
||||||
uid: &bool,
|
uid: &bool,
|
||||||
) -> Result<(Response, flow::Transition)> {
|
) -> Result<(Response, flow::Transition)> {
|
||||||
Ok((
|
Ok((
|
||||||
vec![ImapRes::Status(
|
Response::bad("Not implemented")?,
|
||||||
Status::bad(Some(self.tag.clone()), None, "Not implemented").map_err(Error::msg)?,
|
|
||||||
)],
|
|
||||||
flow::Transition::No,
|
flow::Transition::No,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,7 +92,7 @@ impl Service<Request> for Connection {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call(&mut self, req: Request) -> Self::Future {
|
fn call(&mut self, req: Request) -> Self::Future {
|
||||||
tracing::debug!("Got request: {:#?}", req);
|
tracing::debug!("Got request: {:#?}", req.command);
|
||||||
self.session.process(req)
|
self.session.process(req)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,6 @@ impl Manager {
|
||||||
|
|
||||||
pub fn process(&self, req: Request) -> BoxFuture<'static, Result<Response, BalError>> {
|
pub fn process(&self, req: Request) -> BoxFuture<'static, Result<Response, BalError>> {
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
let tag = req.tag.clone();
|
|
||||||
let msg = Message { req, tx };
|
let msg = Message { req, tx };
|
||||||
|
|
||||||
// We use try_send on a bounded channel to protect the daemons from DoS.
|
// We use try_send on a bounded channel to protect the daemons from DoS.
|
||||||
|
@ -51,17 +50,13 @@ impl Manager {
|
||||||
Ok(()) => (),
|
Ok(()) => (),
|
||||||
Err(TrySendError::Full(_)) => {
|
Err(TrySendError::Full(_)) => {
|
||||||
return async {
|
return async {
|
||||||
Status::bad(Some(tag), None, "Too fast! Send less pipelined requests!")
|
Response::bad("Too fast! Send less pipelined requests.")
|
||||||
.map(|s| vec![ImapRes::Status(s)])
|
|
||||||
.map_err(|e| BalError::Text(e.to_string()))
|
|
||||||
}
|
}
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
Err(TrySendError::Closed(_)) => {
|
Err(TrySendError::Closed(_)) => {
|
||||||
return async {
|
return async {
|
||||||
Status::bad(Some(tag), None, "The session task has exited")
|
Response::bad("Session task has existed.")
|
||||||
.map(|s| vec![ImapRes::Status(s)])
|
|
||||||
.map_err(|e| BalError::Text(e.to_string()))
|
|
||||||
}
|
}
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
@ -73,9 +68,7 @@ impl Manager {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::warn!("Got error {:#?}", e);
|
tracing::warn!("Got error {:#?}", e);
|
||||||
Status::bad(Some(tag), None, "No response from the session handler")
|
Response::bad("No response from the session handler")
|
||||||
.map(|s| vec![ImapRes::Status(s)])
|
|
||||||
.map_err(|e| BalError::Text(e.to_string()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,21 +115,16 @@ impl Instance {
|
||||||
};
|
};
|
||||||
|
|
||||||
// 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 path depending
|
// To prevent state error, we handle the same command in separate code paths.
|
||||||
// on the State.
|
|
||||||
let ctrl = match &self.state {
|
let ctrl = match &self.state {
|
||||||
flow::State::NotAuthenticated => anonymous::dispatch(ctx).await,
|
flow::State::NotAuthenticated => anonymous::dispatch(ctx).await,
|
||||||
flow::State::Authenticated(user) => authenticated::dispatch(ctx, user).await,
|
flow::State::Authenticated(user) => authenticated::dispatch(ctx, user).await,
|
||||||
flow::State::Selected(user, mailbox) => {
|
flow::State::Selected(user, mailbox) => {
|
||||||
selected::dispatch(ctx, user, mailbox).await
|
selected::dispatch(ctx, user, mailbox).await
|
||||||
}
|
}
|
||||||
_ => Status::bad(
|
_ => Response::bad("No commands are allowed in the LOGOUT state.")
|
||||||
Some(ctx.req.tag.clone()),
|
.map(|r| (r, flow::Transition::No))
|
||||||
None,
|
.map_err(Error::msg),
|
||||||
"No commands are allowed in the LOGOUT state.",
|
|
||||||
)
|
|
||||||
.map(|s| (vec![ImapRes::Status(s)], flow::Transition::No))
|
|
||||||
.map_err(Error::msg),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Process result
|
// Process result
|
||||||
|
@ -152,9 +140,7 @@ impl Instance {
|
||||||
Ok(be) => Err(be),
|
Ok(be) => Err(be),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::warn!(error=%e, "internal.error");
|
tracing::warn!(error=%e, "internal.error");
|
||||||
Status::bad(Some(msg.req.tag.clone()), None, "Internal error")
|
Response::bad("Internal error")
|
||||||
.map(|s| vec![ImapRes::Status(s)])
|
|
||||||
.map_err(|e| BalError::Text(e.to_string()))
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue