use std::{
io,
net::{IpAddr, Ipv4Addr, SocketAddr},
path::PathBuf,
sync::{Arc, Mutex},
};
use tokio::{net::TcpListener, sync::broadcast, task::JoinHandle};
#[derive(Clone, Debug)]
pub struct MailboxEvent {
pub user: String,
pub mailbox: String,
pub new_count: usize,
}
pub type MailboxNotifier = broadcast::Sender<MailboxEvent>;
pub use crate::auth::{AuthConfig, SharedUserStore, UserStore};
pub use crate::delivery::{DeliveryPolicy, DsnAction, DsnPolicy, DsnRule, SharedDeliveryPolicy};
pub use crate::store::StorageBackend;
use crate::store::Store;
mod auth;
mod delivery;
mod http;
mod imap;
mod preload;
mod smtp;
mod sqlite_store;
mod store;
mod tls;
#[derive(Clone)]
pub struct ServerConfig {
pub smtp_addr: SocketAddr,
pub imap_addr: SocketAddr,
pub http_addr: Option<SocketAddr>,
pub http_token: Option<String>,
pub preload_dir: Option<PathBuf>,
pub delivery_policy: Option<SharedDeliveryPolicy>,
pub auth: Option<AuthConfig>,
pub storage: StorageBackend,
}
impl std::fmt::Debug for ServerConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let delivery_policy = self.delivery_policy.is_some();
f.debug_struct("ServerConfig")
.field("smtp_addr", &self.smtp_addr)
.field("imap_addr", &self.imap_addr)
.field("http_addr", &self.http_addr)
.field("http_token", &self.http_token)
.field("preload_dir", &self.preload_dir)
.field("delivery_policy", &delivery_policy)
.field("auth", &self.auth)
.field("storage", &self.storage)
.finish()
}
}
impl Default for ServerConfig {
fn default() -> Self {
let loopback = IpAddr::V4(Ipv4Addr::LOCALHOST);
Self {
smtp_addr: SocketAddr::new(loopback, 0),
imap_addr: SocketAddr::new(loopback, 0),
http_addr: None,
http_token: None,
preload_dir: None,
delivery_policy: None,
auth: None,
storage: StorageBackend::InMemory,
}
}
}
pub struct Server;
impl Server {
pub async fn start(config: ServerConfig) -> io::Result<RunningServer> {
let mut store = Store::new(config.storage.clone())?;
if let Some(preload_dir) = config.preload_dir.as_ref() {
preload::load_from_dir(&mut store, preload_dir)?;
} else {
let _ = preload::load_from_env(&mut store)?;
}
let store = Arc::new(Mutex::new(store));
let auth_store = config
.auth
.unwrap_or_else(AuthConfig::from_env)
.into_store();
let auth: SharedUserStore = std::sync::Arc::new(auth_store);
let delivery_policy = config
.delivery_policy
.unwrap_or_else(|| std::sync::Arc::new(crate::delivery::DsnPolicy::from_env()));
let smtp_listener = TcpListener::bind(config.smtp_addr).await?;
let imap_listener = TcpListener::bind(config.imap_addr).await?;
let smtp_addr = smtp_listener.local_addr()?;
let imap_addr = imap_listener.local_addr()?;
let (shutdown_tx, _) = broadcast::channel(4);
let (mailbox_notifier, _) = broadcast::channel::<MailboxEvent>(64);
let mut handles = Vec::new();
let smtp_handle = tokio::spawn(smtp::run_smtp(
smtp_listener,
store.clone(),
auth.clone(),
delivery_policy.clone(),
shutdown_tx.subscribe(),
mailbox_notifier.clone(),
));
handles.push(smtp_handle);
let imap_handle = tokio::spawn(imap::run_imap(
imap_listener,
store.clone(),
auth.clone(),
shutdown_tx.subscribe(),
mailbox_notifier,
));
handles.push(imap_handle);
let http_addr = if let Some(addr) = config.http_addr {
let http_listener = TcpListener::bind(addr).await?;
let bound_addr = http_listener.local_addr()?;
let http_handle = tokio::spawn(http::run_http(
http_listener,
store.clone(),
auth.clone(),
http::HttpMeta {
smtp_addr,
imap_addr,
http_addr: bound_addr,
storage: config.storage.clone(),
http_token: config.http_token.clone(),
},
shutdown_tx.subscribe(),
));
handles.push(http_handle);
Some(bound_addr)
} else {
None
};
Ok(RunningServer {
smtp_addr,
imap_addr,
http_addr,
shutdown_tx,
handles,
})
}
}
pub struct RunningServer {
smtp_addr: SocketAddr,
imap_addr: SocketAddr,
http_addr: Option<SocketAddr>,
shutdown_tx: broadcast::Sender<()>,
handles: Vec<JoinHandle<()>>,
}
impl RunningServer {
pub fn smtp_addr(&self) -> SocketAddr {
self.smtp_addr
}
pub fn imap_addr(&self) -> SocketAddr {
self.imap_addr
}
pub fn http_addr(&self) -> Option<SocketAddr> {
self.http_addr
}
pub async fn stop(self) -> io::Result<()> {
let _ = self.shutdown_tx.send(());
for handle in self.handles {
let _ = handle.await;
}
Ok(())
}
}