//! Smol Mail server. mod channel; mod crypto; mod proto; mod ratelimit; mod server; mod session; mod store; use std::io::Write; #[cfg(unix)] use std::os::unix::fs::OpenOptionsExt; use clap::{Parser, Subcommand}; use crate::proto::DEFAULT_PORT; #[derive(Parser)] #[command(about = "Smol Mail server")] struct Cli { #[arg(short, long, global = true)] verbose: bool, #[command(subcommand)] command: Command, } #[derive(Subcommand)] enum Command { /// Generate the server's static X25519 key Keygen { #[arg(long, default_value = "server.key")] key: String, #[arg(long)] force: bool, }, /// Run the mailbox server Serve { #[arg(long, default_value = "server.key")] key: String, #[arg(long, default_value = "mail.db")] db: String, #[arg(long, default_value = "127.0.0.1")] host: String, #[arg(long, default_value_t = DEFAULT_PORT)] port: u16, #[arg(long = "max-envelope", default_value_t = 1 << 20)] max_envelope: usize, #[arg(long, default_value_t = 64 << 20)] quota: i64, #[arg(long = "retention-days", default_value_t = 30)] retention_days: i64, #[arg(long = "invite-token")] invite_token: Option, #[arg(long = "rate-connections", default_value_t = 120)] rate_connections: u32, #[arg(long = "rate-sends", default_value_t = 60)] rate_sends: u32, }, } fn main() -> anyhow::Result<()> { let cli = Cli::parse(); env_logger::Builder::new() .filter_level(if cli.verbose { log::LevelFilter::Debug } else { log::LevelFilter::Info }) .format_timestamp_secs() .init(); match cli.command { Command::Keygen { key, force } => cmd_keygen(&key, force), Command::Serve { key, db, host, port, max_envelope, quota, retention_days, invite_token, rate_connections, rate_sends, } => server::run(server::ServeArgs { key_path: key, db_path: db, host, port, max_envelope, quota, retention_days, invite_token, rate_connections, rate_sends, }), } } fn cmd_keygen(key_path: &str, force: bool) -> anyhow::Result<()> { if std::path::Path::new(key_path).exists() && !force { anyhow::bail!("{key_path} exists; refusing to overwrite (use --force)"); } let (private, public) = crypto::generate_static_key(); // Written 0600 before any bytes land, so the key is never briefly readable. let mut opts = std::fs::OpenOptions::new(); opts.write(true).create(true).truncate(true); #[cfg(unix)] opts.mode(0o600); let mut file = opts.open(key_path)?; file.write_all(&private)?; println!("private key: {key_path}"); println!("public key: {}", crypto::b32(&public)); println!(); println!("Publish the public key through a trusted channel; clients pin it (SPEC.md sec 4)."); Ok(()) }