123 lines
3.1 KiB
Rust
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(())
|
|
}
|