aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Cargo.lock11
-rw-r--r--Cargo.toml2
-rw-r--r--src/config.rs56
-rw-r--r--src/login/ldap_provider.rs22
-rw-r--r--src/login/mod.rs20
-rw-r--r--src/login/static_provider.rs61
-rw-r--r--src/mailbox.rs112
-rw-r--r--src/main.rs122
9 files changed, 338 insertions, 69 deletions
diff --git a/.gitignore b/.gitignore
index 3151a75..a04ef7f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
/target
.vimrc
test.sh
+mailrage.toml
diff --git a/Cargo.lock b/Cargo.lock
index 5c6e76b..3f85eea 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -556,6 +556,7 @@ name = "mailrage"
version = "0.0.1"
dependencies = [
"anyhow",
+ "async-trait",
"base64",
"hex",
"im",
@@ -570,6 +571,7 @@ dependencies = [
"serde",
"sodiumoxide",
"tokio",
+ "toml",
"zstd",
]
@@ -1204,6 +1206,15 @@ dependencies = [
]
[[package]]
+name = "toml"
+version = "0.5.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
+dependencies = [
+ "serde",
+]
+
+[[package]]
name = "tower-service"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 8f45020..f0c5d7a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -8,6 +8,7 @@ description = "Encrypted mail storage over Garage"
[dependencies]
anyhow = "1.0.28"
+async-trait = "0.1"
base64 = "0.13"
hex = "0.4"
im = "15"
@@ -21,6 +22,7 @@ rand = "0.8.5"
rmp-serde = "0.15"
sodiumoxide = "0.2"
tokio = "1.17.0"
+toml = "0.5"
zstd = { version = "0.9", default-features = false }
k2v-client = { git = "https://git.deuxfleurs.fr/Deuxfleurs/garage.git" }
diff --git a/src/config.rs b/src/config.rs
new file mode 100644
index 0000000..47db90d
--- /dev/null
+++ b/src/config.rs
@@ -0,0 +1,56 @@
+use std::collections::HashMap;
+use std::io::Read;
+use std::path::PathBuf;
+
+use anyhow::Result;
+use serde::Deserialize;
+
+#[derive(Deserialize, Debug, Clone)]
+pub struct Config {
+ pub s3_endpoint: String,
+ pub s3_region: String,
+ pub k2v_endpoint: String,
+ pub k2v_region: String,
+
+ pub login_static: Option<LoginStaticConfig>,
+ pub login_ldap: Option<LoginLdapConfig>,
+}
+
+#[derive(Deserialize, Debug, Clone)]
+pub struct LoginStaticConfig {
+ pub default_bucket: Option<String>,
+ pub users: HashMap<String, LoginStaticUser>,
+}
+
+#[derive(Deserialize, Debug, Clone)]
+pub struct LoginStaticUser {
+ pub password: String,
+ pub aws_access_key_id: String,
+ pub aws_secret_access_key: String,
+ pub bucket: Option<String>,
+ pub master_key: Option<String>,
+}
+
+#[derive(Deserialize, Debug, Clone)]
+pub struct LoginLdapConfig {
+ pub ldap_server: String,
+
+ pub search_dn: String,
+ pub username_attr: String,
+ pub aws_access_key_id_attr: String,
+ pub aws_secret_access_key_attr: String,
+
+ pub bucket: Option<String>,
+ pub bucket_attr: Option<String>,
+}
+
+pub fn read_config(config_file: PathBuf) -> Result<Config> {
+ let mut file = std::fs::OpenOptions::new()
+ .read(true)
+ .open(config_file.as_path())?;
+
+ let mut config = String::new();
+ file.read_to_string(&mut config)?;
+
+ Ok(toml::from_str(&config)?)
+}
diff --git a/src/login/ldap_provider.rs b/src/login/ldap_provider.rs
new file mode 100644
index 0000000..ebe2771
--- /dev/null
+++ b/src/login/ldap_provider.rs
@@ -0,0 +1,22 @@
+use anyhow::Result;
+use async_trait::async_trait;
+
+use crate::config::*;
+use crate::login::*;
+
+pub struct LdapLoginProvider {
+ // TODO
+}
+
+impl LdapLoginProvider {
+ pub fn new(config: LoginLdapConfig) -> Result<Self> {
+ unimplemented!()
+ }
+}
+
+#[async_trait]
+impl LoginProvider for LdapLoginProvider {
+ async fn login(&self, username: &str, password: &str) -> Result<Credentials> {
+ unimplemented!()
+ }
+}
diff --git a/src/login/mod.rs b/src/login/mod.rs
new file mode 100644
index 0000000..5637e8a
--- /dev/null
+++ b/src/login/mod.rs
@@ -0,0 +1,20 @@
+pub mod ldap_provider;
+pub mod static_provider;
+
+use anyhow::Result;
+use async_trait::async_trait;
+
+use crate::cryptoblob::Key as SymmetricKey;
+
+#[derive(Clone, Debug)]
+pub struct Credentials {
+ pub aws_access_key_id: String,
+ pub aws_secret_access_key: String,
+ pub bucket: String,
+ pub master_key: SymmetricKey,
+}
+
+#[async_trait]
+pub trait LoginProvider {
+ async fn login(&self, username: &str, password: &str) -> Result<Credentials>;
+}
diff --git a/src/login/static_provider.rs b/src/login/static_provider.rs
new file mode 100644
index 0000000..037948a
--- /dev/null
+++ b/src/login/static_provider.rs
@@ -0,0 +1,61 @@
+use std::collections::HashMap;
+
+use anyhow::{anyhow, bail, Result};
+use async_trait::async_trait;
+use rusoto_signature::Region;
+
+use crate::config::*;
+use crate::cryptoblob::Key;
+use crate::login::*;
+
+pub struct StaticLoginProvider {
+ default_bucket: Option<String>,
+ users: HashMap<String, LoginStaticUser>,
+ k2v_region: Region,
+}
+
+impl StaticLoginProvider {
+ pub fn new(config: LoginStaticConfig, k2v_region: Region) -> Result<Self> {
+ Ok(Self {
+ default_bucket: config.default_bucket,
+ users: config.users,
+ k2v_region,
+ })
+ }
+}
+
+#[async_trait]
+impl LoginProvider for StaticLoginProvider {
+ async fn login(&self, username: &str, password: &str) -> Result<Credentials> {
+ match self.users.get(username) {
+ None => bail!("User {} does not exist", username),
+ Some(u) => {
+ if u.password != password {
+ // TODO cryptographic password compare
+ bail!("Wrong password");
+ }
+ let bucket = u
+ .bucket
+ .clone()
+ .or_else(|| self.default_bucket.clone())
+ .ok_or(anyhow!(
+ "No bucket configured and no default bucket specieid"
+ ))?;
+
+ // TODO if master key is not specified, retrieve it from K2V key storage
+ let master_key_str = u.master_key.as_ref().ok_or(anyhow!(
+ "Master key must be specified in config file for now, this will change"
+ ))?;
+ let master_key = Key::from_slice(&base64::decode(master_key_str)?)
+ .ok_or(anyhow!("Invalid master key"))?;
+
+ Ok(Credentials {
+ aws_access_key_id: u.aws_access_key_id.clone(),
+ aws_secret_access_key: u.aws_secret_access_key.clone(),
+ bucket,
+ master_key,
+ })
+ }
+ }
+ }
+}
diff --git a/src/mailbox.rs b/src/mailbox.rs
new file mode 100644
index 0000000..7dcb92a
--- /dev/null
+++ b/src/mailbox.rs
@@ -0,0 +1,112 @@
+use anyhow::{anyhow, bail, Result};
+use k2v_client::{BatchDeleteOp, BatchReadOp, Filter, K2vClient, K2vValue};
+use rusoto_core::HttpClient;
+use rusoto_credential::{AwsCredentials, StaticProvider, ProvideAwsCredentials};
+use rusoto_s3::{
+ DeleteObjectRequest, GetObjectRequest, ListObjectsV2Request, PutObjectRequest, S3Client, S3,
+};
+use rusoto_signature::Region;
+use rand::prelude::*;
+
+use crate::cryptoblob::Key;
+use crate::bayou::Bayou;
+use crate::login::Credentials;
+use crate::uidindex::*;
+
+pub struct Mailbox {
+ bucket: String,
+ name: String,
+ key: Key,
+
+ k2v: K2vClient,
+ s3: S3Client,
+
+ uid_index: Bayou<UidIndex>,
+}
+
+impl Mailbox {
+ pub async fn new(k2v_region: Region, s3_region: Region, creds: Credentials,name: String) -> Result<Self> {
+ let aws_creds_provider = StaticProvider::new_minimal(
+ creds.aws_access_key_id,
+ creds.aws_secret_access_key,
+ );
+ let aws_creds = aws_creds_provider.credentials().await?;
+
+ let uid_index = Bayou::<UidIndex>::new(
+ aws_creds.clone(),
+ k2v_region.clone(),
+ s3_region.clone(),
+ creds.bucket.clone(),
+ name.clone(),
+ creds.master_key.clone(),
+ )?;
+
+ let k2v_client = K2vClient::new(k2v_region, creds.bucket.clone(), aws_creds, None)?;
+ let s3_client = S3Client::new_with(HttpClient::new()?, aws_creds_provider, s3_region);
+
+ Ok(Self {
+ bucket: creds.bucket,
+ name,
+ key: creds.master_key,
+ k2v: k2v_client,
+ s3: s3_client,
+ uid_index,
+ })
+ }
+
+
+ pub async fn test(&mut self) -> Result<()> {
+
+ self.uid_index.sync().await?;
+
+ dump(&self.uid_index);
+
+ let mut rand_id = [0u8; 24];
+ rand_id[..16].copy_from_slice(&u128::to_be_bytes(thread_rng().gen()));
+ let add_mail_op = self.uid_index
+ .state()
+ .op_mail_add(MailUuid(rand_id), vec!["\\Unseen".into()]);
+ self.uid_index.push(add_mail_op).await?;
+
+ dump(&self.uid_index);
+
+ if self.uid_index.state().mails_by_uid.len() > 6 {
+ for i in 0..2 {
+ let (_, uuid) = self.uid_index
+ .state()
+ .mails_by_uid
+ .iter()
+ .skip(3 + i)
+ .next()
+ .unwrap();
+ let del_mail_op = self.uid_index.state().op_mail_del(*uuid);
+ self.uid_index.push(del_mail_op).await?;
+
+ dump(&self.uid_index);
+ }
+ }
+
+ Ok(())
+ }
+}
+
+fn dump(uid_index: &Bayou<UidIndex>) {
+ let s = uid_index.state();
+ println!("---- MAILBOX STATE ----");
+ println!("UIDVALIDITY {}", s.uidvalidity);
+ println!("UIDNEXT {}", s.uidnext);
+ println!("INTERNALSEQ {}", s.internalseq);
+ for (uid, uuid) in s.mails_by_uid.iter() {
+ println!(
+ "{} {} {}",
+ uid,
+ hex::encode(uuid.0),
+ s.mail_flags
+ .get(uuid)
+ .cloned()
+ .unwrap_or_default()
+ .join(", ")
+ );
+ }
+ println!("");
+}
diff --git a/src/main.rs b/src/main.rs
index 7b46f01..cf1e886 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,96 +1,80 @@
mod bayou;
+mod config;
mod cryptoblob;
+mod login;
mod time;
mod uidindex;
+mod mailbox;
-use anyhow::Result;
+use anyhow::{bail, Result};
+use std::sync::Arc;
use rand::prelude::*;
use rusoto_credential::{EnvironmentProvider, ProvideAwsCredentials};
use rusoto_signature::Region;
use bayou::*;
+use config::*;
use cryptoblob::Key;
+use login::{ldap_provider::*, static_provider::*, *};
use uidindex::*;
+use mailbox::Mailbox;
#[tokio::main]
async fn main() {
- do_stuff().await.expect("Something failed");
+ if let Err(e) = main2().await {
+ eprintln!("Error: {}", e);
+ std::process::exit(1);
+ }
}
-async fn do_stuff() -> Result<()> {
- let creds = EnvironmentProvider::default().credentials().await.unwrap();
-
- let k2v_region = Region::Custom {
- name: "garage-staging".to_owned(),
- endpoint: "https://k2v-staging.home.adnab.me".to_owned(),
- };
-
- let s3_region = Region::Custom {
- name: "garage-staging".to_owned(),
- endpoint: "https://garage-staging.home.adnab.me".to_owned(),
- };
-
- let key = Key::from_slice(&[0u8; 32]).unwrap();
-
- let mut uid_index = Bayou::<UidIndex>::new(
- creds,
- k2v_region,
- s3_region,
- "mail".into(),
- "TestMailbox".into(),
- key,
- )?;
-
- uid_index.sync().await?;
+async fn main2() -> Result<()> {
+ let config = read_config("mailrage.toml".into())?;
- dump(&uid_index);
+ let main = Main::new(config)?;
+ main.run().await
+}
- let mut rand_id = [0u8; 24];
- rand_id[..16].copy_from_slice(&u128::to_be_bytes(thread_rng().gen()));
- let add_mail_op = uid_index
- .state()
- .op_mail_add(MailUuid(rand_id), vec!["\\Unseen".into()]);
- uid_index.push(add_mail_op).await?;
+struct Main {
+ pub s3_region: Region,
+ pub k2v_region: Region,
+ pub login_provider: Box<dyn LoginProvider>,
+}
- dump(&uid_index);
+impl Main {
+ fn new(config: Config) -> Result<Arc<Self>> {
+ let s3_region = Region::Custom {
+ name: config.s3_region,
+ endpoint: config.s3_endpoint,
+ };
+ let k2v_region = Region::Custom {
+ name: config.k2v_region,
+ endpoint: config.k2v_endpoint,
+ };
+ let login_provider: Box<dyn LoginProvider> = match (config.login_static, config.login_ldap)
+ {
+ (Some(st), None) => Box::new(StaticLoginProvider::new(st, k2v_region.clone())?),
+ (None, Some(ld)) => Box::new(LdapLoginProvider::new(ld)?),
+ (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"),
+ };
+ Ok(Arc::new(Self {
+ s3_region,
+ k2v_region,
+ login_provider,
+ }))
+ }
- if uid_index.state().mails_by_uid.len() > 6 {
- for i in 0..2 {
- let (_, uuid) = uid_index
- .state()
- .mails_by_uid
- .iter()
- .skip(3 + i)
- .next()
- .unwrap();
- let del_mail_op = uid_index.state().op_mail_del(*uuid);
- uid_index.push(del_mail_op).await?;
+ async fn run(self: &Arc<Self>) -> Result<()> {
+ let creds = self.login_provider.login("lx", "plop").await?;
- dump(&uid_index);
- }
- }
+ let mut mailbox = Mailbox::new(self.k2v_region.clone(),
+ self.s3_region.clone(),
+ creds.clone(),
+ "TestMailbox".to_string()).await?;
- Ok(())
-}
+ mailbox.test().await?;
-fn dump(uid_index: &Bayou<UidIndex>) {
- let s = uid_index.state();
- println!("---- MAILBOX STATE ----");
- println!("UIDVALIDITY {}", s.uidvalidity);
- println!("UIDNEXT {}", s.uidnext);
- println!("INTERNALSEQ {}", s.internalseq);
- for (uid, uuid) in s.mails_by_uid.iter() {
- println!(
- "{} {} {}",
- uid,
- hex::encode(uuid.0),
- s.mail_flags
- .get(uuid)
- .cloned()
- .unwrap_or_default()
- .join(", ")
- );
+ Ok(())
}
- println!("");
}