diff options
Diffstat (limited to 'src/main.rs')
-rw-r--r-- | src/main.rs | 407 |
1 files changed, 0 insertions, 407 deletions
diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 43b4dca..0000000 --- a/src/main.rs +++ /dev/null @@ -1,407 +0,0 @@ -use std::io::Read; -use std::path::PathBuf; - -use anyhow::{bail, Context, Result}; -use clap::{Parser, Subcommand}; -use nix::{sys::signal, unistd::Pid}; - -use aerogramme::config::*; -use aerogramme::login::{static_provider::*, *}; -use aerogramme::server::Server; - -#[derive(Parser, Debug)] -#[clap(author, version, about, long_about = None)] -struct Args { - #[clap(subcommand)] - command: Command, - - /// A special mode dedicated to developers, NOT INTENDED FOR PRODUCTION - #[clap(long)] - dev: bool, - - #[clap( - short, - long, - env = "AEROGRAMME_CONFIG", - default_value = "aerogramme.toml" - )] - /// Path to the main Aerogramme configuration file - config_file: PathBuf, -} - -#[derive(Subcommand, Debug)] -enum Command { - #[clap(subcommand)] - /// A daemon to be run by the end user, on a personal device - Companion(CompanionCommand), - - #[clap(subcommand)] - /// A daemon to be run by the service provider, on a server - Provider(ProviderCommand), - - #[clap(subcommand)] - /// Specific tooling, should not be part of a normal workflow, for debug & experimentation only - Tools(ToolsCommand), - //Test, -} - -#[derive(Subcommand, Debug)] -enum ToolsCommand { - /// Manage crypto roots - #[clap(subcommand)] - CryptoRoot(CryptoRootCommand), - - PasswordHash { - #[clap(env = "AEROGRAMME_PASSWORD")] - maybe_password: Option<String>, - }, -} - -#[derive(Subcommand, Debug)] -enum CryptoRootCommand { - /// Generate a new crypto-root protected with a password - New { - #[clap(env = "AEROGRAMME_PASSWORD")] - maybe_password: Option<String>, - }, - /// Generate a new clear text crypto-root, store it securely! - NewClearText, - /// Change the password of a crypto key - ChangePassword { - #[clap(env = "AEROGRAMME_OLD_PASSWORD")] - maybe_old_password: Option<String>, - - #[clap(env = "AEROGRAMME_NEW_PASSWORD")] - maybe_new_password: Option<String>, - - #[clap(short, long, env = "AEROGRAMME_CRYPTO_ROOT")] - crypto_root: String, - }, - /// From a given crypto-key, derive one containing only the public key - DeriveIncoming { - #[clap(short, long, env = "AEROGRAMME_CRYPTO_ROOT")] - crypto_root: String, - }, -} - -#[derive(Subcommand, Debug)] -enum CompanionCommand { - /// Runs the IMAP proxy - Daemon, - Reload { - #[clap(short, long, env = "AEROGRAMME_PID")] - pid: Option<i32>, - }, - Wizard, - #[clap(subcommand)] - Account(AccountManagement), -} - -#[derive(Subcommand, Debug)] -enum ProviderCommand { - /// Runs the IMAP+LMTP server daemon - Daemon, - /// Reload the daemon - Reload { - #[clap(short, long, env = "AEROGRAMME_PID")] - pid: Option<i32>, - }, - /// Manage static accounts - #[clap(subcommand)] - Account(AccountManagement), -} - -#[derive(Subcommand, Debug)] -enum AccountManagement { - /// Add an account - Add { - #[clap(short, long)] - login: String, - #[clap(short, long)] - setup: PathBuf, - }, - /// Delete an account - Delete { - #[clap(short, long)] - login: String, - }, - /// Change password for a given account - ChangePassword { - #[clap(env = "AEROGRAMME_OLD_PASSWORD")] - maybe_old_password: Option<String>, - - #[clap(env = "AEROGRAMME_NEW_PASSWORD")] - maybe_new_password: Option<String>, - - #[clap(short, long)] - login: String, - }, -} - -#[cfg(tokio_unstable)] -fn tracer() { - console_subscriber::init(); -} - -#[cfg(not(tokio_unstable))] -fn tracer() { - tracing_subscriber::fmt::init(); -} - -#[tokio::main] -async fn main() -> Result<()> { - if std::env::var("RUST_LOG").is_err() { - std::env::set_var("RUST_LOG", "main=info,aerogramme=info,k2v_client=info") - } - - // Abort on panic (same behavior as in Go) - std::panic::set_hook(Box::new(|panic_info| { - eprintln!("{}", panic_info); - eprintln!("{:?}", backtrace::Backtrace::new()); - std::process::abort(); - })); - - tracer(); - - let args = Args::parse(); - let any_config = if args.dev { - use std::net::*; - AnyConfig::Provider(ProviderConfig { - pid: None, - imap: None, - imap_unsecure: Some(ImapUnsecureConfig { - bind_addr: SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), 1143), - }), - dav_unsecure: Some(DavUnsecureConfig { - bind_addr: SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), 8087), - }), - lmtp: Some(LmtpConfig { - bind_addr: SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), 1025), - hostname: "example.tld".to_string(), - }), - auth: Some(AuthConfig { - bind_addr: SocketAddr::new( - IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), - 12345, - ), - }), - users: UserManagement::Demo, - }) - } else { - read_config(args.config_file)? - }; - - match (&args.command, any_config) { - (Command::Companion(subcommand), AnyConfig::Companion(config)) => match subcommand { - CompanionCommand::Daemon => { - let server = Server::from_companion_config(config).await?; - server.run().await?; - } - CompanionCommand::Reload { pid } => reload(*pid, config.pid)?, - CompanionCommand::Wizard => { - unimplemented!(); - } - CompanionCommand::Account(cmd) => { - let user_file = config.users.user_list; - account_management(&args.command, cmd, user_file)?; - } - }, - (Command::Provider(subcommand), AnyConfig::Provider(config)) => match subcommand { - ProviderCommand::Daemon => { - let server = Server::from_provider_config(config).await?; - server.run().await?; - } - ProviderCommand::Reload { pid } => reload(*pid, config.pid)?, - ProviderCommand::Account(cmd) => { - let user_file = match config.users { - UserManagement::Static(conf) => conf.user_list, - _ => { - panic!("Only static account management is supported from Aerogramme.") - } - }; - account_management(&args.command, cmd, user_file)?; - } - }, - (Command::Provider(_), AnyConfig::Companion(_)) => { - bail!("Your want to run a 'Provider' command but your configuration file has role 'Companion'."); - } - (Command::Companion(_), AnyConfig::Provider(_)) => { - bail!("Your want to run a 'Companion' command but your configuration file has role 'Provider'."); - } - (Command::Tools(subcommand), _) => match subcommand { - ToolsCommand::PasswordHash { maybe_password } => { - let password = match maybe_password { - Some(pwd) => pwd.clone(), - None => rpassword::prompt_password("Enter password: ")?, - }; - println!("{}", hash_password(&password)?); - } - ToolsCommand::CryptoRoot(crcommand) => match crcommand { - CryptoRootCommand::New { maybe_password } => { - let password = match maybe_password { - Some(pwd) => pwd.clone(), - None => { - let password = rpassword::prompt_password("Enter password: ")?; - let password_confirm = - rpassword::prompt_password("Confirm password: ")?; - if password != password_confirm { - bail!("Passwords don't match."); - } - password - } - }; - let crypto_keys = CryptoKeys::init(); - let cr = CryptoRoot::create_pass(&password, &crypto_keys)?; - println!("{}", cr.0); - } - CryptoRootCommand::NewClearText => { - let crypto_keys = CryptoKeys::init(); - let cr = CryptoRoot::create_cleartext(&crypto_keys); - println!("{}", cr.0); - } - CryptoRootCommand::ChangePassword { - maybe_old_password, - maybe_new_password, - crypto_root, - } => { - let old_password = match maybe_old_password { - Some(pwd) => pwd.to_string(), - None => rpassword::prompt_password("Enter old password: ")?, - }; - - let new_password = match maybe_new_password { - Some(pwd) => pwd.to_string(), - None => { - let password = rpassword::prompt_password("Enter new password: ")?; - let password_confirm = - rpassword::prompt_password("Confirm new password: ")?; - if password != password_confirm { - bail!("Passwords don't match."); - } - password - } - }; - - let keys = CryptoRoot(crypto_root.to_string()).crypto_keys(&old_password)?; - let cr = CryptoRoot::create_pass(&new_password, &keys)?; - println!("{}", cr.0); - } - CryptoRootCommand::DeriveIncoming { crypto_root } => { - let pubkey = CryptoRoot(crypto_root.to_string()).public_key()?; - let cr = CryptoRoot::create_incoming(&pubkey); - println!("{}", cr.0); - } - }, - }, - } - - Ok(()) -} - -fn reload(pid: Option<i32>, pid_path: Option<PathBuf>) -> Result<()> { - let final_pid = match (pid, pid_path) { - (Some(pid), _) => pid, - (_, Some(path)) => { - let mut f = std::fs::OpenOptions::new().read(true).open(path)?; - let mut pidstr = String::new(); - f.read_to_string(&mut pidstr)?; - pidstr.parse::<i32>()? - } - _ => bail!("Unable to infer your daemon's PID"), - }; - let pid = Pid::from_raw(final_pid); - signal::kill(pid, signal::Signal::SIGUSR1)?; - Ok(()) -} - -fn account_management(root: &Command, cmd: &AccountManagement, users: PathBuf) -> Result<()> { - let mut ulist: UserList = - read_config(users.clone()).context(format!("'{:?}' must be a user database", users))?; - - match cmd { - AccountManagement::Add { login, setup } => { - tracing::debug!(user = login, "will-create"); - let stp: SetupEntry = read_config(setup.clone()) - .context(format!("'{:?}' must be a setup file", setup))?; - tracing::debug!(user = login, "loaded setup entry"); - - let password = match stp.clear_password { - Some(pwd) => pwd, - None => { - let password = rpassword::prompt_password("Enter password: ")?; - let password_confirm = rpassword::prompt_password("Confirm password: ")?; - if password != password_confirm { - bail!("Passwords don't match."); - } - password - } - }; - - let crypto_keys = CryptoKeys::init(); - let crypto_root = match root { - Command::Provider(_) => CryptoRoot::create_pass(&password, &crypto_keys)?, - Command::Companion(_) => CryptoRoot::create_cleartext(&crypto_keys), - _ => unreachable!(), - }; - - let hash = hash_password(password.as_str()).context("unable to hash password")?; - - ulist.insert( - login.clone(), - UserEntry { - email_addresses: stp.email_addresses, - password: hash, - crypto_root: crypto_root.0, - storage: stp.storage, - }, - ); - - write_config(users.clone(), &ulist)?; - } - AccountManagement::Delete { login } => { - tracing::debug!(user = login, "will-delete"); - ulist.remove(login); - write_config(users.clone(), &ulist)?; - } - AccountManagement::ChangePassword { - maybe_old_password, - maybe_new_password, - login, - } => { - let mut user = ulist.remove(login).context("user must exist first")?; - - let old_password = match maybe_old_password { - Some(pwd) => pwd.to_string(), - None => rpassword::prompt_password("Enter old password: ")?, - }; - - if !verify_password(&old_password, &user.password)? { - bail!(format!("invalid password for login {}", login)); - } - - let crypto_keys = CryptoRoot(user.crypto_root).crypto_keys(&old_password)?; - - let new_password = match maybe_new_password { - Some(pwd) => pwd.to_string(), - None => { - let password = rpassword::prompt_password("Enter new password: ")?; - let password_confirm = rpassword::prompt_password("Confirm new password: ")?; - if password != password_confirm { - bail!("Passwords don't match."); - } - password - } - }; - let new_hash = hash_password(&new_password)?; - let new_crypto_root = CryptoRoot::create_pass(&new_password, &crypto_keys)?; - - user.password = new_hash; - user.crypto_root = new_crypto_root.0; - - ulist.insert(login.clone(), user); - write_config(users.clone(), &ulist)?; - } - }; - - Ok(()) -} |