use super::config;
use super::*;
use crate::api::{
bucket_permission, group, mail, mailbody, mailbox, mailboxgrouproot, mailfolder, permission,
salt, session, user,
};
use crate::{crypto, http_client::HttpClient};
use anyhow::{anyhow, bail, Context, Result};
use lz4_flex::decompress_into;
use tracing::debug;
use types::{
Aes128Key, Base64, BucketPermission, BucketPermissionType, GroupType, Id, Mail, MailFolderType,
Permission, PermissionType, ReadStatus, User,
};
use websocket::WebSocketConnector;
use async_stream::try_stream;
use futures_core::stream::Stream;
pub struct Client {
config: config::Account,
client: HttpClient,
inboxes: Vec<String>,
user: User,
}
#[derive(Debug)]
pub struct MailContent {
pub subject: Option<String>,
pub name: Option<String>,
pub address: String,
pub body: Option<String>,
}
struct SessionData {
client: HttpClient,
user_passphrase_key: Aes128Key,
user_id: Id,
}
impl Client {
pub async fn new(config: &config::Account) -> Result<Client> {
let SessionData {
client,
user_passphrase_key,
user_id,
} = Self::create_session(config).await?;
let mut user = user::fetch(&client, &user_id).await?;
user.unlock_group_keys(&user_passphrase_key);
let mail_member = user
.memberships
.iter()
.find(|membership| membership.group_type == GroupType::Mail)
.context("Could not find group with type mail")?;
let root = mailboxgrouproot::fetch(&client, &mail_member.group).await?;
let mailbox = mailbox::fetch(&client, &root).await?;
let folders = mailfolder::fetch(&client, &mailbox).await?;
let inboxes: Vec<_> = folders
.into_iter()
.filter(|folder| {
folder.folder_type == MailFolderType::Inbox
|| (config.watch_spam && folder.folder_type == MailFolderType::Spam)
})
.map(|folder| folder.mails)
.collect();
Ok(Client {
config: config.clone(),
client,
inboxes,
user,
})
}
async fn create_session(config: &config::Account) -> Result<SessionData> {
let mut client = HttpClient::new();
let salt = salt::fetch(&client, &config.email_address).await?;
let user_passphrase_key = crypto::create_user_passphrase_key(&config.password, &salt);
let session = session::fetch(&client, &config.email_address, &user_passphrase_key).await?;
client.set_access_token(session.access_token);
Ok(SessionData {
client,
user_passphrase_key,
user_id: session.user,
})
}
pub fn get_mails(&self) -> impl Stream<Item = Result<Mail>> + '_ {
try_stream! {
for inbox in &self.inboxes {
let mut start = None;
let curr_mails = mail::fetch_from_inbox(&self.client, &inbox, start).await?;
let mut n = curr_mails.len();
let mut last = curr_mails.last().map_or("".into(), |m| m.id.1.clone());
for mail in curr_mails {
yield mail
}
while n > 0 {
start = Some(last);
let curr_mails = mail::fetch_from_inbox(&self.client, &inbox, start).await?;
last = curr_mails.last().map_or("".into(), |m| m.id.1.clone());
n = curr_mails.len();
for mail in curr_mails {
yield mail
}
}
}
}
}
fn resolve_session_key_owner(&self, mail: &Mail) -> Result<Aes128Key> {
debug!("resolve session key with owner key");
let gk = self
.user
.get_group_key(&mail.owner_group)
.context("No group key for mail")?;
let key = mail
.owner_enc_session_key
.context("No owner enc session key for mail")?;
return Ok(crypto::decrypt_key(&gk, &key));
}
fn try_symmetric_permission(&self, perms: &Vec<Permission>) -> Option<Aes128Key> {
debug!("try symmetric permission");
let sym_perm = perms.iter().find(|p| {
p.permission_type == PermissionType::PublicSymmetric
|| p.permission_type == PermissionType::Symmetric
&& p.owner_group
.as_ref()
.is_some_and(|g| self.user.has_group(&g))
&& p.owner_enc_session_key.is_some()
});
if let Some(sym) = sym_perm {
let gk = self
.user
.get_group_key(&sym.owner_group.as_ref().unwrap())
.unwrap();
let sk = sym.owner_enc_session_key.unwrap();
Some(crypto::decrypt_key(&gk, &sk))
} else {
None
}
}
async fn resolve_session_key_public_external(
&self,
perms: &Vec<Permission>,
) -> Result<Aes128Key> {
debug!("resolve session key from public or external bucket");
let pub_or_external_perm = perms
.iter()
.find(|p| {
p.permission_type == PermissionType::Public
|| p.permission_type == PermissionType::External
})
.context("could not find public or external permission")?;
let bucket_perm_id = &pub_or_external_perm
.bucket
.clone()
.context("Bucket is null")?
.bucket_permissions;
let bucket_permissions = bucket_permission::fetch(&self.client, &bucket_perm_id).await?;
let bucket_permission = bucket_permissions
.iter()
.find(|p| {
p.permission_type == BucketPermissionType::Public
|| p.permission_type == BucketPermissionType::External
})
.context("could not find public or external permission")?;
match bucket_permission.permission_type {
BucketPermissionType::External => {
self.resolve_external_bucket(&bucket_permission, &pub_or_external_perm)
}
BucketPermissionType::Public => {
self.resolve_public_bucket(&bucket_permission, &pub_or_external_perm)
.await
}
}
}
fn resolve_external_bucket(
&self,
bucket_perm: &BucketPermission,
perm: &Permission,
) -> Result<Aes128Key> {
debug!("decrypt with external bucket");
let bucket_key;
if let Some(bk) = bucket_perm.owner_enc_bucket_key {
bucket_key = crypto::decrypt_key(
&self
.user
.get_group_key(&bucket_perm.owner_group.as_ref().unwrap())
.unwrap(),
&bk,
);
} else if let Some(sym) = bucket_perm.sym_enc_bucket_key {
bucket_key = crypto::decrypt_key(&self.user.get_user_group_key(), &sym);
} else {
bail!("BucketEncSessionKey is not defined for Permission")
}
let msg = perm
.bucket_enc_session_key
.context("bucket enc session key not defined")?;
Ok(crypto::decrypt_key(&bucket_key, &msg))
}
async fn resolve_public_bucket(
&self,
bucket_perm: &BucketPermission,
perm: &Permission,
) -> Result<Aes128Key> {
debug!("decrypt with public bucket");
let pub_enc_bucket_key = bucket_perm
.pub_enc_bucket_key
.clone()
.context("PubEncBucketKey is not defined")?;
let bucket_enc_session_key = perm
.bucket_enc_session_key
.context("BucktEncSessionKey is not defined")?;
let bucket_key = self
.decrypt_bucket_key_key_pair_group(&bucket_perm.group, &pub_enc_bucket_key)
.await?;
let sk = crypto::decrypt_key(&bucket_key, &bucket_enc_session_key);
Ok(sk)
}
async fn decrypt_bucket_key_key_pair_group(
&self,
key_pair: &Id,
pub_enc_bucket_key: &Base64,
) -> Result<Aes128Key> {
debug!("decrypt bucket key with key pair of group");
let group = group::fetch(&self.client, &key_pair).await?;
let key_pair = &group.keys[0];
let priv_key = crypto::decrypt_rsa_key(
&self.user.get_group_key(&group.id).unwrap(),
&key_pair.sym_enc_priv_key,
)?;
crypto::rsa_decrypt(&priv_key, pub_enc_bucket_key)?
.try_into()
.map_err(|_| anyhow!("Could not convert to [u8; 16]"))
}
async fn resolve_session_key(&self, mail: &Mail) -> Result<Aes128Key> {
debug!("Resolve session key");
if mail.owner_enc_session_key.is_some() && self.user.has_group(&mail.owner_group) {
self.resolve_session_key_owner(mail)
} else {
let perms = permission::fetch(&self.client, &mail.permissions).await?;
Ok(self
.try_symmetric_permission(&perms)
.unwrap_or(self.resolve_session_key_public_external(&perms).await?))
}
}
pub async fn decrypt(&self, mail: &Mail) -> Result<MailContent> {
let session_key = self.resolve_session_key(mail).await?;
let subject = if self.config.show_subject {
let tmp = crypto::aes_decrypt(&session_key, &mail.subject)?;
Some(
std::str::from_utf8(&tmp)
.expect("Subject could not converted to UTF-8")
.to_string(),
)
} else {
None
};
let name = if self.config.show_name {
let tmp = crypto::aes_decrypt(&session_key, &mail.sender.name)?;
Some(
std::str::from_utf8(&tmp)
.expect("Name could not converted to UTF-8")
.to_string(),
)
} else {
None
};
let address = mail.sender.address.to_string();
let body = if self.config.show_body {
let mailbody = mailbody::fetch(&self.client, &mail.body).await?;
let compressed_text = crypto::aes_decrypt(&session_key, &mailbody)?;
let mut buf: Vec<u8> = vec![0; mailbody.len() * 6];
let size = decompress_into(&compressed_text, &mut buf)?;
buf.resize(size, 0);
Some(
std::str::from_utf8(&buf)
.expect("Body could not be converted to UTF-8")
.to_string(),
)
} else {
None
};
Ok(MailContent {
subject,
name,
address,
body,
})
}
pub async fn set_read_status(&self, mail: &mut Mail, read_status: ReadStatus) -> Result<()> {
if mail.read_status == read_status {
return Ok(());
}
mail.read_status = read_status;
mail::update(&self.client, &mail).await?;
Ok(())
}
pub fn get_websocket_connector(&self) -> Result<WebSocketConnector> {
WebSocketConnector::from_url(&self.client, &self.user.id)
}
}