More construction of things
This commit is contained in:
parent
8bd59a8f83
commit
c7f767b7d2
9 changed files with 339 additions and 70 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
||||||
/target
|
/target
|
||||||
.vimrc
|
.vimrc
|
||||||
test.sh
|
test.sh
|
||||||
|
mailrage.toml
|
||||||
|
|
11
Cargo.lock
generated
11
Cargo.lock
generated
|
@ -556,6 +556,7 @@ name = "mailrage"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"async-trait",
|
||||||
"base64",
|
"base64",
|
||||||
"hex",
|
"hex",
|
||||||
"im",
|
"im",
|
||||||
|
@ -570,6 +571,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"sodiumoxide",
|
"sodiumoxide",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"toml",
|
||||||
"zstd",
|
"zstd",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1203,6 +1205,15 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml"
|
||||||
|
version = "0.5.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower-service"
|
name = "tower-service"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
|
|
|
@ -8,6 +8,7 @@ description = "Encrypted mail storage over Garage"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.28"
|
anyhow = "1.0.28"
|
||||||
|
async-trait = "0.1"
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
im = "15"
|
im = "15"
|
||||||
|
@ -21,6 +22,7 @@ rand = "0.8.5"
|
||||||
rmp-serde = "0.15"
|
rmp-serde = "0.15"
|
||||||
sodiumoxide = "0.2"
|
sodiumoxide = "0.2"
|
||||||
tokio = "1.17.0"
|
tokio = "1.17.0"
|
||||||
|
toml = "0.5"
|
||||||
zstd = { version = "0.9", default-features = false }
|
zstd = { version = "0.9", default-features = false }
|
||||||
|
|
||||||
k2v-client = { git = "https://git.deuxfleurs.fr/Deuxfleurs/garage.git" }
|
k2v-client = { git = "https://git.deuxfleurs.fr/Deuxfleurs/garage.git" }
|
||||||
|
|
56
src/config.rs
Normal file
56
src/config.rs
Normal file
|
@ -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)?)
|
||||||
|
}
|
22
src/login/ldap_provider.rs
Normal file
22
src/login/ldap_provider.rs
Normal file
|
@ -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!()
|
||||||
|
}
|
||||||
|
}
|
20
src/login/mod.rs
Normal file
20
src/login/mod.rs
Normal file
|
@ -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>;
|
||||||
|
}
|
61
src/login/static_provider.rs
Normal file
61
src/login/static_provider.rs
Normal file
|
@ -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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
112
src/mailbox.rs
Normal file
112
src/mailbox.rs
Normal file
|
@ -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!("");
|
||||||
|
}
|
124
src/main.rs
124
src/main.rs
|
@ -1,96 +1,80 @@
|
||||||
mod bayou;
|
mod bayou;
|
||||||
|
mod config;
|
||||||
mod cryptoblob;
|
mod cryptoblob;
|
||||||
|
mod login;
|
||||||
mod time;
|
mod time;
|
||||||
mod uidindex;
|
mod uidindex;
|
||||||
|
mod mailbox;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::{bail, Result};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use rusoto_credential::{EnvironmentProvider, ProvideAwsCredentials};
|
use rusoto_credential::{EnvironmentProvider, ProvideAwsCredentials};
|
||||||
use rusoto_signature::Region;
|
use rusoto_signature::Region;
|
||||||
|
|
||||||
use bayou::*;
|
use bayou::*;
|
||||||
|
use config::*;
|
||||||
use cryptoblob::Key;
|
use cryptoblob::Key;
|
||||||
|
use login::{ldap_provider::*, static_provider::*, *};
|
||||||
use uidindex::*;
|
use uidindex::*;
|
||||||
|
use mailbox::Mailbox;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn 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<()> {
|
async fn main2() -> Result<()> {
|
||||||
let creds = EnvironmentProvider::default().credentials().await.unwrap();
|
let config = read_config("mailrage.toml".into())?;
|
||||||
|
|
||||||
let k2v_region = Region::Custom {
|
let main = Main::new(config)?;
|
||||||
name: "garage-staging".to_owned(),
|
main.run().await
|
||||||
endpoint: "https://k2v-staging.home.adnab.me".to_owned(),
|
}
|
||||||
};
|
|
||||||
|
|
||||||
let s3_region = Region::Custom {
|
struct Main {
|
||||||
name: "garage-staging".to_owned(),
|
pub s3_region: Region,
|
||||||
endpoint: "https://garage-staging.home.adnab.me".to_owned(),
|
pub k2v_region: Region,
|
||||||
};
|
pub login_provider: Box<dyn LoginProvider>,
|
||||||
|
}
|
||||||
|
|
||||||
let key = Key::from_slice(&[0u8; 32]).unwrap();
|
impl Main {
|
||||||
|
fn new(config: Config) -> Result<Arc<Self>> {
|
||||||
let mut uid_index = Bayou::<UidIndex>::new(
|
let s3_region = Region::Custom {
|
||||||
creds,
|
name: config.s3_region,
|
||||||
k2v_region,
|
endpoint: config.s3_endpoint,
|
||||||
s3_region,
|
};
|
||||||
"mail".into(),
|
let k2v_region = Region::Custom {
|
||||||
"TestMailbox".into(),
|
name: config.k2v_region,
|
||||||
key,
|
endpoint: config.k2v_endpoint,
|
||||||
)?;
|
};
|
||||||
|
let login_provider: Box<dyn LoginProvider> = match (config.login_static, config.login_ldap)
|
||||||
uid_index.sync().await?;
|
{
|
||||||
|
(Some(st), None) => Box::new(StaticLoginProvider::new(st, k2v_region.clone())?),
|
||||||
dump(&uid_index);
|
(None, Some(ld)) => Box::new(LdapLoginProvider::new(ld)?),
|
||||||
|
(Some(_), Some(_)) => bail!("A single login provider must be set up in config file"),
|
||||||
let mut rand_id = [0u8; 24];
|
(None, None) => bail!("No login provider is set up in config file"),
|
||||||
rand_id[..16].copy_from_slice(&u128::to_be_bytes(thread_rng().gen()));
|
};
|
||||||
let add_mail_op = uid_index
|
Ok(Arc::new(Self {
|
||||||
.state()
|
s3_region,
|
||||||
.op_mail_add(MailUuid(rand_id), vec!["\\Unseen".into()]);
|
k2v_region,
|
||||||
uid_index.push(add_mail_op).await?;
|
login_provider,
|
||||||
|
}))
|
||||||
dump(&uid_index);
|
|
||||||
|
|
||||||
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?;
|
|
||||||
|
|
||||||
dump(&uid_index);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
async fn run(self: &Arc<Self>) -> Result<()> {
|
||||||
}
|
let creds = self.login_provider.login("lx", "plop").await?;
|
||||||
|
|
||||||
fn dump(uid_index: &Bayou<UidIndex>) {
|
let mut mailbox = Mailbox::new(self.k2v_region.clone(),
|
||||||
let s = uid_index.state();
|
self.s3_region.clone(),
|
||||||
println!("---- MAILBOX STATE ----");
|
creds.clone(),
|
||||||
println!("UIDVALIDITY {}", s.uidvalidity);
|
"TestMailbox".to_string()).await?;
|
||||||
println!("UIDNEXT {}", s.uidnext);
|
|
||||||
println!("INTERNALSEQ {}", s.internalseq);
|
mailbox.test().await?;
|
||||||
for (uid, uuid) in s.mails_by_uid.iter() {
|
|
||||||
println!(
|
Ok(())
|
||||||
"{} {} {}",
|
|
||||||
uid,
|
|
||||||
hex::encode(uuid.0),
|
|
||||||
s.mail_flags
|
|
||||||
.get(uuid)
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or_default()
|
|
||||||
.join(", ")
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
println!("");
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue