aerogramme/src/imap/session.rs

181 lines
6 KiB
Rust
Raw Normal View History

2022-06-17 10:28:02 +00:00
use anyhow::Error;
2022-06-13 09:44:02 +00:00
use boitalettres::errors::Error as BalError;
2022-06-14 08:19:24 +00:00
use boitalettres::proto::{Request, Response};
2022-06-13 09:44:02 +00:00
use futures::future::BoxFuture;
use futures::future::FutureExt;
2022-06-29 10:52:58 +00:00
2022-06-14 08:19:24 +00:00
use tokio::sync::mpsc::error::TrySendError;
use tokio::sync::{mpsc, oneshot};
2022-06-13 09:44:02 +00:00
2022-06-30 11:36:21 +00:00
use crate::imap::command::{anonymous, authenticated, examined, selected};
2022-06-20 16:09:20 +00:00
use crate::imap::flow;
use crate::login::ArcLoginProvider;
2022-06-13 09:44:02 +00:00
/* This constant configures backpressure in the system,
* or more specifically, how many pipelined messages are allowed
2022-06-14 08:19:24 +00:00
* before refusing them
2022-06-13 09:44:02 +00:00
*/
const MAX_PIPELINED_COMMANDS: usize = 10;
struct Message {
req: Request,
2022-06-13 09:58:33 +00:00
tx: oneshot::Sender<Result<Response, BalError>>,
2022-06-13 09:44:02 +00:00
}
2022-06-17 16:39:36 +00:00
//-----
2022-06-13 09:44:02 +00:00
pub struct Manager {
tx: mpsc::Sender<Message>,
}
impl Manager {
2022-06-17 16:39:36 +00:00
pub fn new(login_provider: ArcLoginProvider) -> Self {
2022-06-14 08:19:24 +00:00
let (tx, rx) = mpsc::channel(MAX_PIPELINED_COMMANDS);
tokio::spawn(async move {
2022-06-29 10:52:58 +00:00
let instance = Instance::new(login_provider, rx);
2022-06-13 09:44:02 +00:00
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(()) => (),
2022-06-14 08:19:24 +00:00
Err(TrySendError::Full(_)) => {
2022-06-28 08:49:28 +00:00
return async { Response::bad("Too fast! Send less pipelined requests.") }.boxed()
2022-06-14 08:19:24 +00:00
}
Err(TrySendError::Closed(_)) => {
2022-06-28 13:52:55 +00:00
return async { Err(BalError::Text("Terminated session".to_string())) }.boxed()
2022-06-14 08:19:24 +00:00
}
2022-06-13 09:44:02 +00:00
};
// @FIXME add a timeout, handle a session that fails.
async {
2022-06-13 09:58:33 +00:00
match rx.await {
Ok(r) => r,
Err(e) => {
tracing::warn!("Got error {:#?}", e);
2022-06-28 07:34:24 +00:00
Response::bad("No response from the session handler")
2022-06-14 08:19:24 +00:00
}
2022-06-13 09:58:33 +00:00
}
2022-06-14 08:19:24 +00:00
}
.boxed()
2022-06-13 09:44:02 +00:00
}
}
2022-06-17 16:39:36 +00:00
//-----
2022-06-13 09:44:02 +00:00
pub struct Instance {
rx: mpsc::Receiver<Message>,
2022-06-17 16:39:36 +00:00
pub login_provider: ArcLoginProvider,
pub state: flow::State,
2022-06-13 09:44:02 +00:00
}
impl Instance {
2022-06-22 15:26:52 +00:00
fn new(login_provider: ArcLoginProvider, rx: mpsc::Receiver<Message>) -> Self {
2022-06-14 08:19:24 +00:00
Self {
2022-06-15 16:40:39 +00:00
login_provider,
2022-06-14 08:19:24 +00:00
rx,
2022-06-17 16:39:36 +00:00
state: flow::State::NotAuthenticated,
2022-06-14 08:19:24 +00:00
}
2022-06-13 09:44:02 +00:00
}
//@FIXME add a function that compute the runner's name from its local info
// to ease debug
// fn name(&self) -> String { }
2022-06-22 12:58:57 +00:00
async fn start(mut self) {
2022-06-13 09:44:02 +00:00
//@FIXME add more info about the runner
tracing::debug!("starting runner");
while let Some(msg) = self.rx.recv().await {
2022-06-17 16:39:36 +00:00
// Command behavior is modulated by the state.
2022-06-28 07:34:24 +00:00
// To prevent state error, we handle the same command in separate code paths.
2022-06-29 10:50:44 +00:00
let ctrl = match &mut self.state {
flow::State::NotAuthenticated => {
let ctx = anonymous::AnonymousContext {
req: &msg.req,
login_provider: Some(&self.login_provider),
};
anonymous::dispatch(ctx).await
}
flow::State::Authenticated(ref user) => {
let ctx = authenticated::AuthenticatedContext {
req: &msg.req,
user,
};
authenticated::dispatch(ctx).await
}
flow::State::Selected(ref user, ref mut mailbox) => {
let ctx = selected::SelectedContext {
req: &msg.req,
user,
mailbox,
};
selected::dispatch(ctx).await
}
flow::State::Examined(ref user, ref mut mailbox) => {
let ctx = examined::ExaminedContext {
req: &msg.req,
user,
mailbox,
};
examined::dispatch(ctx).await
}
2022-06-29 10:50:44 +00:00
flow::State::Logout => {
Response::bad("No commands are allowed in the LOGOUT state.")
.map(|r| (r, flow::Transition::None))
.map_err(Error::msg)
2022-06-22 15:26:52 +00:00
}
2022-06-13 09:44:02 +00:00
};
2022-06-13 09:58:33 +00:00
2022-06-22 12:58:57 +00:00
// Process result
let res = match ctrl {
Ok((res, tr)) => {
2022-06-28 08:49:28 +00:00
//@FIXME remove unwrap
2022-07-13 13:26:00 +00:00
self.state = match self.state.apply(tr) {
Ok(new_state) => new_state,
Err(e) => {
tracing::error!("Invalid transition: {}, exiting", e);
break;
}
};
2022-06-28 08:49:28 +00:00
2022-06-28 13:38:06 +00:00
//@FIXME enrich here the command with some global status
2022-06-28 08:49:28 +00:00
2022-06-22 12:58:57 +00:00
Ok(res)
2022-06-22 15:26:52 +00:00
}
2022-06-22 12:58:57 +00:00
// 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");
2022-06-28 07:34:24 +00:00
Response::bad("Internal error")
2022-06-22 12:58:57 +00:00
}
2022-06-22 15:26:52 +00:00
},
2022-06-22 12:58:57 +00:00
};
2022-06-13 16:01:07 +00:00
2022-06-13 09:58:33 +00:00
//@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.
2022-06-22 12:58:57 +00:00
msg.tx.send(res).unwrap_or_else(|e| {
2022-06-14 08:19:24 +00:00
tracing::warn!("failed to send imap response to manager: {:#?}", e)
});
2022-06-28 13:52:55 +00:00
if let flow::State::Logout = &self.state {
break;
}
2022-06-22 15:26:52 +00:00
}
2022-06-20 16:09:20 +00:00
2022-06-22 15:26:52 +00:00
//@FIXME add more info about the runner
tracing::debug!("exiting runner");
}
2022-06-13 09:44:02 +00:00
}