bunshin/src/main.rs

123 lines
3.1 KiB
Rust

//! 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<String>,
#[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(())
}