CLI skeleton
This commit is contained in:
parent
6be90936a1
commit
1dcb11643c
10 changed files with 381 additions and 54 deletions
121
Cargo.lock
generated
121
Cargo.lock
generated
|
@ -19,6 +19,17 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "atty"
|
||||||
|
version = "0.2.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||||
|
dependencies = [
|
||||||
|
"hermit-abi",
|
||||||
|
"libc",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
@ -95,6 +106,45 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap"
|
||||||
|
version = "3.1.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d2dbdf4bdacb33466e854ce889eee8dfd5729abf7ccd7664d0a2d60cd384440b"
|
||||||
|
dependencies = [
|
||||||
|
"atty",
|
||||||
|
"bitflags",
|
||||||
|
"clap_derive",
|
||||||
|
"clap_lex",
|
||||||
|
"indexmap",
|
||||||
|
"lazy_static",
|
||||||
|
"strsim",
|
||||||
|
"termcolor",
|
||||||
|
"textwrap",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_derive"
|
||||||
|
version = "3.1.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "25320346e922cffe59c0bbc5410c8d8784509efb321488971081313cb1e1a33c"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro-error",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_lex"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213"
|
||||||
|
dependencies = [
|
||||||
|
"os_str_bytes",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.9.3"
|
version = "0.9.3"
|
||||||
|
@ -349,6 +399,12 @@ version = "0.11.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.1.19"
|
version = "0.1.19"
|
||||||
|
@ -558,12 +614,14 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"base64",
|
"base64",
|
||||||
|
"clap",
|
||||||
"hex",
|
"hex",
|
||||||
"im",
|
"im",
|
||||||
"itertools",
|
"itertools",
|
||||||
"k2v-client",
|
"k2v-client",
|
||||||
"rand",
|
"rand",
|
||||||
"rmp-serde",
|
"rmp-serde",
|
||||||
|
"rpassword",
|
||||||
"rusoto_core",
|
"rusoto_core",
|
||||||
"rusoto_credential",
|
"rusoto_credential",
|
||||||
"rusoto_s3",
|
"rusoto_s3",
|
||||||
|
@ -708,6 +766,12 @@ dependencies = [
|
||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "os_str_bytes"
|
||||||
|
version = "6.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "029d8d0b2f198229de29dca79676f2738ff952edf3fde542eb8bf94d8c21b435"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "paste"
|
name = "paste"
|
||||||
version = "1.0.7"
|
version = "1.0.7"
|
||||||
|
@ -744,6 +808,30 @@ version = "0.2.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
|
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-error"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-error-attr",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-error-attr"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.39"
|
version = "1.0.39"
|
||||||
|
@ -852,6 +940,18 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rpassword"
|
||||||
|
version = "6.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2bf099a1888612545b683d2661a1940089f6c2e5a8e38979b2159da876bfd956"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rusoto_core"
|
name = "rusoto_core"
|
||||||
version = "0.48.0"
|
version = "0.48.0"
|
||||||
|
@ -1100,6 +1200,12 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "subtle"
|
name = "subtle"
|
||||||
version = "2.4.1"
|
version = "2.4.1"
|
||||||
|
@ -1131,6 +1237,21 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termcolor"
|
||||||
|
version = "1.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "textwrap"
|
||||||
|
version = "0.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.31"
|
version = "1.0.31"
|
||||||
|
|
|
@ -10,6 +10,7 @@ description = "Encrypted mail storage over Garage"
|
||||||
anyhow = "1.0.28"
|
anyhow = "1.0.28"
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
|
clap = { version = "3.1.18", features = ["derive", "env"] }
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
im = "15"
|
im = "15"
|
||||||
itertools = "0.10"
|
itertools = "0.10"
|
||||||
|
@ -20,6 +21,7 @@ rusoto_signature = "0.48.0"
|
||||||
serde = "1.0.137"
|
serde = "1.0.137"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
rmp-serde = "0.15"
|
rmp-serde = "0.15"
|
||||||
|
rpassword = "6.0"
|
||||||
sodiumoxide = "0.2"
|
sodiumoxide = "0.2"
|
||||||
tokio = "1.17.0"
|
tokio = "1.17.0"
|
||||||
toml = "0.5"
|
toml = "0.5"
|
||||||
|
|
|
@ -56,10 +56,7 @@ pub struct Bayou<S: BayouState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: BayouState> Bayou<S> {
|
impl<S: BayouState> Bayou<S> {
|
||||||
pub fn new(
|
pub fn new(creds: &Credentials, path: String) -> Result<Self> {
|
||||||
creds: &Credentials,
|
|
||||||
path: String,
|
|
||||||
) -> Result<Self> {
|
|
||||||
let k2v_client = creds.k2v_client()?;
|
let k2v_client = creds.k2v_client()?;
|
||||||
let s3_client = creds.s3_client()?;
|
let s3_client = creds.s3_client()?;
|
||||||
|
|
||||||
|
|
|
@ -8,9 +8,8 @@ use serde::Deserialize;
|
||||||
#[derive(Deserialize, Debug, Clone)]
|
#[derive(Deserialize, Debug, Clone)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub s3_endpoint: String,
|
pub s3_endpoint: String,
|
||||||
pub s3_region: String,
|
|
||||||
pub k2v_endpoint: String,
|
pub k2v_endpoint: String,
|
||||||
pub k2v_region: String,
|
pub aws_region: String,
|
||||||
|
|
||||||
pub login_static: Option<LoginStaticConfig>,
|
pub login_static: Option<LoginStaticConfig>,
|
||||||
pub login_ldap: Option<LoginLdapConfig>,
|
pub login_ldap: Option<LoginLdapConfig>,
|
||||||
|
|
|
@ -5,14 +5,16 @@ use anyhow::{anyhow, Result};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use zstd::stream::{decode_all as zstd_decode, encode_all as zstd_encode};
|
use zstd::stream::{decode_all as zstd_decode, encode_all as zstd_encode};
|
||||||
|
|
||||||
use sodiumoxide::crypto::secretbox::xsalsa20poly1305 as secretbox;
|
|
||||||
use sodiumoxide::crypto::box_ as publicbox;
|
use sodiumoxide::crypto::box_ as publicbox;
|
||||||
|
use sodiumoxide::crypto::secretbox::xsalsa20poly1305 as secretbox;
|
||||||
|
|
||||||
|
pub use sodiumoxide::crypto::box_::{
|
||||||
|
gen_keypair, PublicKey, SecretKey, PUBLICKEYBYTES, SECRETKEYBYTES,
|
||||||
|
};
|
||||||
pub use sodiumoxide::crypto::secretbox::xsalsa20poly1305::{gen_key, Key, KEYBYTES};
|
pub use sodiumoxide::crypto::secretbox::xsalsa20poly1305::{gen_key, Key, KEYBYTES};
|
||||||
pub use sodiumoxide::crypto::box_::{gen_keypair, PublicKey, SecretKey, PUBLICKEYBYTES, SECRETKEYBYTES};
|
|
||||||
|
|
||||||
pub fn open(cryptoblob: &[u8], key: &Key) -> Result<Vec<u8>> {
|
pub fn open(cryptoblob: &[u8], key: &Key) -> Result<Vec<u8>> {
|
||||||
use secretbox::{NONCEBYTES, Nonce};
|
use secretbox::{Nonce, NONCEBYTES};
|
||||||
|
|
||||||
if cryptoblob.len() < NONCEBYTES {
|
if cryptoblob.len() < NONCEBYTES {
|
||||||
return Err(anyhow!("Cyphertext too short"));
|
return Err(anyhow!("Cyphertext too short"));
|
||||||
|
@ -31,7 +33,7 @@ pub fn open(cryptoblob: &[u8], key: &Key) -> Result<Vec<u8>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn seal(plainblob: &[u8], key: &Key) -> Result<Vec<u8>> {
|
pub fn seal(plainblob: &[u8], key: &Key) -> Result<Vec<u8>> {
|
||||||
use secretbox::{NONCEBYTES, gen_nonce};
|
use secretbox::{gen_nonce, NONCEBYTES};
|
||||||
|
|
||||||
// Compress data using zstd
|
// Compress data using zstd
|
||||||
let mut reader = &plainblob[..];
|
let mut reader = &plainblob[..];
|
||||||
|
|
|
@ -53,12 +53,6 @@ impl Credentials {
|
||||||
pub fn bucket(&self) -> &str {
|
pub fn bucket(&self) -> &str {
|
||||||
self.storage.bucket.as_str()
|
self.storage.bucket.as_str()
|
||||||
}
|
}
|
||||||
pub fn dump_config(&self) {
|
|
||||||
println!("aws_access_key_id = \"{}\"", self.storage.aws_access_key_id);
|
|
||||||
println!("aws_secret_access_key = \"{}\"", self.storage.aws_secret_access_key);
|
|
||||||
println!("master_key = \"{}\"", base64::encode(&self.keys.master));
|
|
||||||
println!("secret_key = \"{}\"", base64::encode(&self.keys.secret));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StorageCredentials {
|
impl StorageCredentials {
|
||||||
|
@ -93,28 +87,40 @@ impl StorageCredentials {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CryptoKeys {
|
impl CryptoKeys {
|
||||||
pub fn init(storage: &StorageCredentials) -> Result<Self> {
|
pub async fn init(storage: &StorageCredentials, password: &str) -> Result<Self> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init_without_password(storage: &StorageCredentials, master_key: &Key, secret_key: &SecretKey) -> Result<Self> {
|
pub async fn init_without_password(
|
||||||
|
storage: &StorageCredentials,
|
||||||
|
master_key: &Key,
|
||||||
|
secret_key: &SecretKey,
|
||||||
|
) -> Result<Self> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open(storage: &StorageCredentials, password: &str) -> Result<Self> {
|
pub async fn open(storage: &StorageCredentials, password: &str) -> Result<Self> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open_without_password(storage: &StorageCredentials, master_key: &Key, secret_key: &SecretKey) -> Result<Self> {
|
pub async fn open_without_password(
|
||||||
|
storage: &StorageCredentials,
|
||||||
|
master_key: &Key,
|
||||||
|
secret_key: &SecretKey,
|
||||||
|
) -> Result<Self> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_password(&self, storage: &StorageCredentials, password: &str) -> Result<()> {
|
pub async fn add_password(&self, storage: &StorageCredentials, password: &str) -> Result<()> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_password(&self, storage: &StorageCredentials, password: &str, allow_remove_all: bool) -> Result<()> {
|
pub async fn delete_password(
|
||||||
|
&self,
|
||||||
|
storage: &StorageCredentials,
|
||||||
|
password: &str,
|
||||||
|
allow_delete_all: bool,
|
||||||
|
) -> Result<()> {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -58,19 +58,24 @@ impl LoginProvider for StaticLoginProvider {
|
||||||
.ok_or(anyhow!("Invalid master key"))?;
|
.ok_or(anyhow!("Invalid master key"))?;
|
||||||
let secret_key = SecretKey::from_slice(&base64::decode(m)?)
|
let secret_key = SecretKey::from_slice(&base64::decode(m)?)
|
||||||
.ok_or(anyhow!("Invalid secret key"))?;
|
.ok_or(anyhow!("Invalid secret key"))?;
|
||||||
CryptoKeys::open_without_password(&storage, &master_key, &secret_key)?
|
CryptoKeys::open_without_password(&storage, &master_key, &secret_key).await?
|
||||||
}
|
}
|
||||||
(None, None) => {
|
(None, None) => {
|
||||||
CryptoKeys::open(&storage, password)?
|
CryptoKeys::open(&storage, password).await?
|
||||||
}
|
}
|
||||||
_ => bail!("Either both master and secret key or none of them must be specified for user"),
|
_ => bail!("Either both master and secret key or none of them must be specified for user"),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Credentials {
|
Ok(Credentials { storage, keys })
|
||||||
storage,
|
|
||||||
keys,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn hash_password(password: &str) -> String {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_password(password: &str, hash: &str) -> bool {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
|
@ -21,10 +21,7 @@ pub struct Mailbox {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mailbox {
|
impl Mailbox {
|
||||||
pub async fn new(
|
pub async fn new(creds: &Credentials, name: String) -> Result<Self> {
|
||||||
creds: &Credentials,
|
|
||||||
name: String,
|
|
||||||
) -> Result<Self> {
|
|
||||||
let uid_index = Bayou::<UidIndex>::new(creds, name.clone())?;
|
let uid_index = Bayou::<UidIndex>::new(creds, name.clone())?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
|
226
src/main.rs
226
src/main.rs
|
@ -3,32 +3,236 @@ mod config;
|
||||||
mod cryptoblob;
|
mod cryptoblob;
|
||||||
mod login;
|
mod login;
|
||||||
mod mailbox;
|
mod mailbox;
|
||||||
|
mod server;
|
||||||
mod time;
|
mod time;
|
||||||
mod uidindex;
|
mod uidindex;
|
||||||
mod server;
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use std::sync::Arc;
|
use clap::{Parser, Subcommand};
|
||||||
|
use rand::prelude::*;
|
||||||
|
|
||||||
use rusoto_signature::Region;
|
use rusoto_signature::Region;
|
||||||
|
|
||||||
use config::*;
|
use config::*;
|
||||||
|
use cryptoblob::*;
|
||||||
use login::{ldap_provider::*, static_provider::*, *};
|
use login::{ldap_provider::*, static_provider::*, *};
|
||||||
use mailbox::Mailbox;
|
use mailbox::Mailbox;
|
||||||
use server::Server;
|
use server::Server;
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[clap(author, version, about, long_about = None)]
|
||||||
|
struct Args {
|
||||||
|
#[clap(subcommand)]
|
||||||
|
command: Command,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand, Debug)]
|
||||||
|
enum Command {
|
||||||
|
/// Runs the IMAP+LMTP server daemon
|
||||||
|
Server {
|
||||||
|
#[clap(short, long, env = "CONFIG_FILE", default_value = "mailrage.toml")]
|
||||||
|
config_file: PathBuf,
|
||||||
|
},
|
||||||
|
/// Initializes key pairs for a user and adds a key decryption password
|
||||||
|
FirstLogin {
|
||||||
|
#[clap(flatten)]
|
||||||
|
creds: StorageCredsArgs,
|
||||||
|
},
|
||||||
|
/// Initializes key pairs for a user and dumps keys to stdout for usage with static
|
||||||
|
/// login provider
|
||||||
|
InitializeLocalKeys {
|
||||||
|
#[clap(flatten)]
|
||||||
|
creds: StorageCredsArgs,
|
||||||
|
},
|
||||||
|
/// Adds a key decryption password for a user
|
||||||
|
AddPassword {
|
||||||
|
#[clap(flatten)]
|
||||||
|
creds: StorageCredsArgs,
|
||||||
|
/// Automatically generate password
|
||||||
|
#[clap(short, long)]
|
||||||
|
gen: bool,
|
||||||
|
},
|
||||||
|
/// Deletes a key decription password for a user
|
||||||
|
DeletePassword {
|
||||||
|
#[clap(flatten)]
|
||||||
|
creds: StorageCredsArgs,
|
||||||
|
/// Allow to delete all passwords
|
||||||
|
#[clap(long)]
|
||||||
|
allow_delete_all: bool,
|
||||||
|
},
|
||||||
|
/// Dumps all encryption keys for user
|
||||||
|
ShowKeys {
|
||||||
|
#[clap(flatten)]
|
||||||
|
creds: StorageCredsArgs,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
struct StorageCredsArgs {
|
||||||
|
/// Name of the region to use
|
||||||
|
#[clap(short = 'r', long, env = "AWS_REGION")]
|
||||||
|
region: String,
|
||||||
|
/// Url of the endpoint to connect to for K2V
|
||||||
|
#[clap(short = 'k', long, env = "K2V_ENDPOINT")]
|
||||||
|
k2v_endpoint: String,
|
||||||
|
/// Url of the endpoint to connect to for S3
|
||||||
|
#[clap(short = 's', long, env = "S3_ENDPOINT")]
|
||||||
|
s3_endpoint: String,
|
||||||
|
/// Access key ID
|
||||||
|
#[clap(short = 'A', long, env = "AWS_ACCESS_KEY_ID")]
|
||||||
|
aws_access_key_id: String,
|
||||||
|
/// Access key ID
|
||||||
|
#[clap(short = 'S', long, env = "AWS_SECRET_ACCESS_KEY")]
|
||||||
|
aws_secret_access_key: String,
|
||||||
|
/// Bucket name
|
||||||
|
#[clap(short = 'b', long, env = "BUCKET")]
|
||||||
|
bucket: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() -> Result<()> {
|
||||||
if let Err(e) = main2().await {
|
let args = Args::parse();
|
||||||
eprintln!("Error: {}", e);
|
|
||||||
std::process::exit(1);
|
match args.command {
|
||||||
|
Command::Server { config_file } => {
|
||||||
|
let config = read_config(config_file)?;
|
||||||
|
|
||||||
|
let server = Server::new(config)?;
|
||||||
|
server.run().await?;
|
||||||
|
}
|
||||||
|
Command::FirstLogin { creds } => {
|
||||||
|
let creds = make_storage_creds(creds);
|
||||||
|
|
||||||
|
println!("Please enter your password for key decryption.");
|
||||||
|
println!("If you are using LDAP login, this must be your LDAP password.");
|
||||||
|
println!("If you are using the static login provider, enter any password, and this will also become your password for local IMAP access.");
|
||||||
|
let password = rpassword::prompt_password("Enter password: ")?;
|
||||||
|
let password_confirm = rpassword::prompt_password("Confirm password: ")?;
|
||||||
|
if password != password_confirm {
|
||||||
|
bail!("Passwords don't match.");
|
||||||
|
}
|
||||||
|
|
||||||
|
CryptoKeys::init(&creds, &password).await?;
|
||||||
|
|
||||||
|
println!("");
|
||||||
|
println!("Cryptographic key setup is complete.");
|
||||||
|
println!("");
|
||||||
|
println!("If you are using the static login provider, add the following section to your .toml configuration file:");
|
||||||
|
println!("");
|
||||||
|
dump_config(&password, &creds);
|
||||||
|
}
|
||||||
|
Command::InitializeLocalKeys { creds } => {
|
||||||
|
let creds = make_storage_creds(creds);
|
||||||
|
|
||||||
|
println!("Please enter a password for local IMAP access.");
|
||||||
|
println!("This password is not used for key decryption, your keys will be printed below (do not lose them!)");
|
||||||
|
println!(
|
||||||
|
"If you plan on using LDAP login, stop right here and use `first-login` instead"
|
||||||
|
);
|
||||||
|
let password = rpassword::prompt_password("Enter password: ")?;
|
||||||
|
let password_confirm = rpassword::prompt_password("Confirm password: ")?;
|
||||||
|
if password != password_confirm {
|
||||||
|
bail!("Passwords don't match.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let master = gen_key();
|
||||||
|
let (_, secret) = gen_keypair();
|
||||||
|
let keys = CryptoKeys::init_without_password(&creds, &master, &secret).await?;
|
||||||
|
|
||||||
|
println!("");
|
||||||
|
println!("Cryptographic key setup is complete.");
|
||||||
|
println!("");
|
||||||
|
println!("Add the following section to your .toml configuration file:");
|
||||||
|
println!("");
|
||||||
|
dump_config(&password, &creds);
|
||||||
|
dump_keys(&keys);
|
||||||
|
}
|
||||||
|
Command::AddPassword { creds, gen } => {
|
||||||
|
let creds = make_storage_creds(creds);
|
||||||
|
let existing_password =
|
||||||
|
rpassword::prompt_password("Enter existing password to decrypt keys: ")?;
|
||||||
|
let new_password = if gen {
|
||||||
|
let password = base64::encode(&u128::to_be_bytes(thread_rng().gen())[..10]);
|
||||||
|
println!("Your new password: {}", password);
|
||||||
|
println!("Keep it safe!");
|
||||||
|
password
|
||||||
|
} else {
|
||||||
|
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 = CryptoKeys::open(&creds, &existing_password).await?;
|
||||||
|
keys.add_password(&creds, &new_password).await?;
|
||||||
|
println!("");
|
||||||
|
println!("New password added successfully.");
|
||||||
|
}
|
||||||
|
Command::DeletePassword {
|
||||||
|
creds,
|
||||||
|
allow_delete_all,
|
||||||
|
} => {
|
||||||
|
let creds = make_storage_creds(creds);
|
||||||
|
let existing_password = rpassword::prompt_password("Enter password to delete: ")?;
|
||||||
|
|
||||||
|
let keys = CryptoKeys::open(&creds, &existing_password).await?;
|
||||||
|
keys.delete_password(&creds, &existing_password, allow_delete_all)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!("");
|
||||||
|
println!("Password was deleted successfully.");
|
||||||
|
|
||||||
|
if allow_delete_all {
|
||||||
|
println!("As a reminder, here are your cryptographic keys:");
|
||||||
|
dump_keys(&keys);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Command::ShowKeys { creds } => {
|
||||||
|
let creds = make_storage_creds(creds);
|
||||||
|
let existing_password = rpassword::prompt_password("Enter key decryption password: ")?;
|
||||||
|
|
||||||
|
let keys = CryptoKeys::open(&creds, &existing_password).await?;
|
||||||
|
dump_keys(&keys);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_storage_creds(c: StorageCredsArgs) -> StorageCredentials {
|
||||||
|
let s3_region = Region::Custom {
|
||||||
|
name: c.region.clone(),
|
||||||
|
endpoint: c.s3_endpoint,
|
||||||
|
};
|
||||||
|
let k2v_region = Region::Custom {
|
||||||
|
name: c.region,
|
||||||
|
endpoint: c.k2v_endpoint,
|
||||||
|
};
|
||||||
|
StorageCredentials {
|
||||||
|
k2v_region,
|
||||||
|
s3_region,
|
||||||
|
aws_access_key_id: c.aws_access_key_id,
|
||||||
|
aws_secret_access_key: c.aws_secret_access_key,
|
||||||
|
bucket: c.bucket,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn main2() -> Result<()> {
|
fn dump_config(password: &str, creds: &StorageCredentials) {
|
||||||
let config = read_config("mailrage.toml".into())?;
|
println!("[login_static.users.<username>]");
|
||||||
|
println!("password = \"{}\"", hash_password(password)); //TODO
|
||||||
let server = Server::new(config)?;
|
println!("aws_access_key_id = \"{}\"", creds.aws_access_key_id);
|
||||||
server.run().await
|
println!(
|
||||||
|
"aws_secret_access_key = \"{}\"",
|
||||||
|
creds.aws_secret_access_key
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn dump_keys(keys: &CryptoKeys) {
|
||||||
|
println!("master_key = \"{}\"", base64::encode(&keys.master));
|
||||||
|
println!("secret_key = \"{}\"", base64::encode(&keys.secret));
|
||||||
|
}
|
||||||
|
|
|
@ -14,11 +14,11 @@ pub struct Server {
|
||||||
impl Server {
|
impl Server {
|
||||||
pub fn new(config: Config) -> Result<Arc<Self>> {
|
pub fn new(config: Config) -> Result<Arc<Self>> {
|
||||||
let s3_region = Region::Custom {
|
let s3_region = Region::Custom {
|
||||||
name: config.s3_region,
|
name: config.aws_region.clone(),
|
||||||
endpoint: config.s3_endpoint,
|
endpoint: config.s3_endpoint,
|
||||||
};
|
};
|
||||||
let k2v_region = Region::Custom {
|
let k2v_region = Region::Custom {
|
||||||
name: config.k2v_region,
|
name: config.aws_region,
|
||||||
endpoint: config.k2v_endpoint,
|
endpoint: config.k2v_endpoint,
|
||||||
};
|
};
|
||||||
let login_provider: Box<dyn LoginProvider> = match (config.login_static, config.login_ldap)
|
let login_provider: Box<dyn LoginProvider> = match (config.login_static, config.login_ldap)
|
||||||
|
@ -28,19 +28,13 @@ impl Server {
|
||||||
(Some(_), Some(_)) => bail!("A single login provider must be set up in config file"),
|
(Some(_), Some(_)) => bail!("A single login provider must be set up in config file"),
|
||||||
(None, None) => bail!("No login provider is set up in config file"),
|
(None, None) => bail!("No login provider is set up in config file"),
|
||||||
};
|
};
|
||||||
Ok(Arc::new(Self {
|
Ok(Arc::new(Self { login_provider }))
|
||||||
login_provider,
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(self: &Arc<Self>) -> Result<()> {
|
pub async fn run(self: &Arc<Self>) -> Result<()> {
|
||||||
let creds = self.login_provider.login("lx", "plop").await?;
|
let creds = self.login_provider.login("lx", "plop").await?;
|
||||||
|
|
||||||
let mut mailbox = Mailbox::new(
|
let mut mailbox = Mailbox::new(&creds, "TestMailbox".to_string()).await?;
|
||||||
&creds,
|
|
||||||
"TestMailbox".to_string(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
mailbox.test().await?;
|
mailbox.test().await?;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue