mirror of
https://github.com/chatmail/core.git
synced 2026-05-03 05:16:28 +03:00
Implement message generation using lettre_email
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -641,6 +641,7 @@ dependencies = [
|
|||||||
"lettre_email 0.9.2 (git+https://github.com/deltachat/lettre?branch=feat/rustls)",
|
"lettre_email 0.9.2 (git+https://github.com/deltachat/lettre?branch=feat/rustls)",
|
||||||
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
"libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"mailparse 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"mailparse 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"mime 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"num-derive 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"num-derive 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
"num-traits 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ rustls = "0.16.0"
|
|||||||
webpki-roots = "0.18.0"
|
webpki-roots = "0.18.0"
|
||||||
webpki = "0.21.0"
|
webpki = "0.21.0"
|
||||||
mailparse = "0.10.1"
|
mailparse = "0.10.1"
|
||||||
|
mime = "0.3.14"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3.0"
|
tempfile = "3.0"
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ use crate::config::Config;
|
|||||||
use crate::constants::*;
|
use crate::constants::*;
|
||||||
use crate::contact::*;
|
use crate::contact::*;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::dc_strencode::*;
|
|
||||||
use crate::dc_tools::*;
|
use crate::dc_tools::*;
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::events::Event;
|
use crate::events::Event;
|
||||||
|
|||||||
164
src/e2ee.rs
164
src/e2ee.rs
@@ -54,101 +54,94 @@ impl EncryptHelper {
|
|||||||
Aheader::new(addr, pk, self.prefer_encrypt)
|
Aheader::new(addr, pk, self.prefer_encrypt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Determines if we can and should encrypt.
|
||||||
|
pub fn should_encrypt(
|
||||||
|
&self,
|
||||||
|
context: &Context,
|
||||||
|
e2ee_guaranteed: bool,
|
||||||
|
peerstates: &[(Option<Peerstate>, &str)],
|
||||||
|
) -> Result<bool> {
|
||||||
|
if !(self.prefer_encrypt == EncryptPreference::Mutual || e2ee_guaranteed) {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (peerstate, addr) in peerstates {
|
||||||
|
match peerstate {
|
||||||
|
Some(peerstate) => {
|
||||||
|
if peerstate.prefer_encrypt != EncryptPreference::Mutual && !e2ee_guaranteed {
|
||||||
|
info!(context, "peerstate for {:?} is no-encrypt", addr);
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let msg = format!("peerstate for {:?} missing, cannot encrypt", addr);
|
||||||
|
if e2ee_guaranteed {
|
||||||
|
return Err(format_err!("{}", msg));
|
||||||
|
} else {
|
||||||
|
info!(context, "{}", msg);
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tries to encrypt the passed in `mail`.
|
||||||
pub fn try_encrypt(
|
pub fn try_encrypt(
|
||||||
&mut self,
|
&mut self,
|
||||||
factory: &mut MimeFactory,
|
factory: &mut MimeFactory,
|
||||||
e2ee_guaranteed: bool,
|
e2ee_guaranteed: bool,
|
||||||
min_verified: PeerstateVerifiedStatus,
|
min_verified: PeerstateVerifiedStatus,
|
||||||
do_gossip: bool,
|
do_gossip: bool,
|
||||||
mut in_out_message: lettre_email::Email,
|
mut mail: lettre_email::Email,
|
||||||
imffields_unprotected: Vec<lettre_email::Header>,
|
imffields_unprotected: Vec<lettre_email::Header>,
|
||||||
) -> Result<lettre_email::Email> {
|
peerstates: &[(Option<Peerstate>, &str)],
|
||||||
unimplemented!()
|
) -> Result<(lettre_email::Email, bool)> {
|
||||||
// // libEtPan's pgp_encrypt_mime() takes the parent as the new root.
|
let context = &factory.context;
|
||||||
// // We just expect the root as being given to this function.
|
let mut keyring = Keyring::default();
|
||||||
// ensure!(
|
let mut gossip_headers: Vec<String> = Vec::with_capacity(factory.recipients_addr.len());
|
||||||
// !in_out_message.is_null() && { (*in_out_message).mm_parent.is_null() },
|
|
||||||
// "corrupted inputs"
|
|
||||||
// );
|
|
||||||
|
|
||||||
// if !(self.prefer_encrypt == EncryptPreference::Mutual || e2ee_guaranteed) {
|
for (peerstate, addr) in peerstates
|
||||||
// return Ok(false);
|
.iter()
|
||||||
// }
|
.filter_map(|(state, addr)| state.as_ref().map(|s| (s, addr)))
|
||||||
|
{
|
||||||
|
if let Some(key) = peerstate.peek_key(min_verified) {
|
||||||
|
keyring.add_owned(key.clone());
|
||||||
|
if do_gossip {
|
||||||
|
if let Some(header) = peerstate.render_gossip_header(min_verified) {
|
||||||
|
gossip_headers.push(header.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bail!("proper enc-key for {} missing, cannot encrypt", addr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// let context = &factory.context;
|
// libEtPan's pgp_encrypt_mime() takes the parent as the new root.
|
||||||
// let mut keyring = Keyring::default();
|
// We just expect the root as being given to this function.
|
||||||
// let mut gossip_headers: Vec<String> = Vec::with_capacity(factory.recipients_addr.len());
|
let sign_key = {
|
||||||
|
keyring.add_ref(&self.public_key);
|
||||||
|
let key = Key::from_self_private(context, self.addr.clone(), &context.sql);
|
||||||
|
ensure!(key.is_some(), "no own private key found");
|
||||||
|
|
||||||
// // determine if we can and should encrypt
|
key
|
||||||
// for recipient_addr in factory.recipients_addr.iter() {
|
};
|
||||||
// if recipient_addr == &self.addr {
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
// let peerstate = match Peerstate::from_addr(context, &context.sql, recipient_addr) {
|
|
||||||
// Some(peerstate) => peerstate,
|
|
||||||
// None => {
|
|
||||||
// let msg = format!("peerstate for {} missing, cannot encrypt", recipient_addr);
|
|
||||||
// if e2ee_guaranteed {
|
|
||||||
// return Err(format_err!("{}", msg));
|
|
||||||
// } else {
|
|
||||||
// info!(context, "{}", msg);
|
|
||||||
// return Ok(false);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
|
|
||||||
// if peerstate.prefer_encrypt != EncryptPreference::Mutual && !e2ee_guaranteed {
|
// encrypt message
|
||||||
// info!(context, "peerstate for {} is no-encrypt", recipient_addr);
|
use lettre_email::{EmailBuilder, MimeMessage, PartBuilder};
|
||||||
// return Ok(false);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if let Some(key) = peerstate.peek_key(min_verified) {
|
// let mut part_to_encrypt: lettre::SendableEmail = mail.into().to_string()?.into_bytes();
|
||||||
// keyring.add_owned(key.clone());
|
|
||||||
// if do_gossip {
|
|
||||||
// if let Some(header) = peerstate.render_gossip_header(min_verified) {
|
|
||||||
// gossip_headers.push(header.to_string());
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// bail!(
|
|
||||||
// "proper enc-key for {} missing, cannot encrypt",
|
|
||||||
// recipient_addr
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// let sign_key = {
|
let mut mail_to_encrypt = EmailBuilder::new();
|
||||||
// keyring.add_ref(&self.public_key);
|
|
||||||
// let key = Key::from_self_private(context, self.addr.clone(), &context.sql);
|
|
||||||
// ensure!(key.is_some(), "no own private key found");
|
|
||||||
|
|
||||||
// key
|
for header in &gossip_headers {
|
||||||
// };
|
mail_to_encrypt = mail_to_encrypt.header(("Autocrypt-Gossip", header));
|
||||||
|
}
|
||||||
|
|
||||||
// // encrypt message
|
// memoryhole headers: move some headers into encrypted part
|
||||||
// {
|
|
||||||
// mailprivacy_prepare_mime(in_out_message);
|
|
||||||
// let mut part_to_encrypt = (*in_out_message).mm_data.mm_message.mm_msg_mime;
|
|
||||||
// (*part_to_encrypt).mm_parent = ptr::null_mut();
|
|
||||||
// let imffields_encrypted = mailimf_fields_new_empty();
|
|
||||||
|
|
||||||
// // mailmime_new_message_data() calls mailmime_fields_new_with_version()
|
|
||||||
// // which would add the unwanted MIME-Version:-header
|
|
||||||
// let message_to_encrypt = mailmime_new_simple(
|
|
||||||
// MAILMIME_MESSAGE as libc::c_int,
|
|
||||||
// mailmime_fields_new_empty(),
|
|
||||||
// mailmime_get_content_message(),
|
|
||||||
// imffields_encrypted,
|
|
||||||
// part_to_encrypt,
|
|
||||||
// );
|
|
||||||
|
|
||||||
// for header in &gossip_headers {
|
|
||||||
// wrapmime::new_custom_field(imffields_encrypted, "Autocrypt-Gossip", &header)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // memoryhole headers: move some headers into encrypted part
|
|
||||||
// // XXX note we can't use clist's into_iter() because the loop body also removes items
|
|
||||||
// let mut cur = (*(*imffields_unprotected).fld_list).first;
|
|
||||||
// while !cur.is_null() {
|
// while !cur.is_null() {
|
||||||
// let field = (*cur).data as *mut mailimf_field;
|
// let field = (*cur).data as *mut mailimf_field;
|
||||||
// let mut move_to_encrypted = false;
|
// let mut move_to_encrypted = false;
|
||||||
@@ -234,11 +227,11 @@ impl EncryptHelper {
|
|||||||
// mailmime_smart_add_part(encrypted_part, ctext_part);
|
// mailmime_smart_add_part(encrypted_part, ctext_part);
|
||||||
// (*in_out_message).mm_data.mm_message.mm_msg_mime = encrypted_part;
|
// (*in_out_message).mm_data.mm_message.mm_msg_mime = encrypted_part;
|
||||||
// (*encrypted_part).mm_parent = in_out_message;
|
// (*encrypted_part).mm_parent = in_out_message;
|
||||||
// let gossiped = !&gossip_headers.is_empty();
|
|
||||||
// factory.finalize_mime_message(in_out_message, true, gossiped)?;
|
|
||||||
|
|
||||||
// Ok(true)
|
// let gossiped = !&gossip_headers.is_empty();
|
||||||
// }
|
// factory.finalize_mime_message(true, gossiped)?;
|
||||||
|
|
||||||
|
Ok((mail_to_encrypt.build()?, true))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -531,11 +524,6 @@ fn decrypt_part(
|
|||||||
public_keyring_for_validate: &Keyring,
|
public_keyring_for_validate: &Keyring,
|
||||||
ret_valid_signatures: &mut HashSet<String>,
|
ret_valid_signatures: &mut HashSet<String>,
|
||||||
) -> Result<Option<Vec<u8>>> {
|
) -> Result<Option<Vec<u8>>> {
|
||||||
ensure!(
|
|
||||||
wrapmime::has_decryptable_data(mail),
|
|
||||||
"No decryptable data found"
|
|
||||||
);
|
|
||||||
|
|
||||||
let data = mail.get_body_raw()?;
|
let data = mail.get_body_raw()?;
|
||||||
|
|
||||||
if has_decrypted_pgp_armor(&data) {
|
if has_decrypted_pgp_armor(&data) {
|
||||||
|
|||||||
18
src/error.rs
18
src/error.rs
@@ -29,7 +29,11 @@ pub enum Error {
|
|||||||
#[fail(display = "Watch folder not found {:?}", _0)]
|
#[fail(display = "Watch folder not found {:?}", _0)]
|
||||||
WatchFolderNotFound(String),
|
WatchFolderNotFound(String),
|
||||||
#[fail(display = "Inalid Email: {:?}", _0)]
|
#[fail(display = "Inalid Email: {:?}", _0)]
|
||||||
MailParseError(mailparse::MailParseError),
|
MailParseError(#[cause] mailparse::MailParseError),
|
||||||
|
#[fail(display = "Building invalid Email: {:?}", _0)]
|
||||||
|
LettreError(#[cause] lettre_email::error::Error),
|
||||||
|
#[fail(display = "FromStr error: {:?}", _0)]
|
||||||
|
FromStr(#[cause] mime::FromStrError),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
@@ -106,6 +110,18 @@ impl From<mailparse::MailParseError> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<lettre_email::error::Error> for Error {
|
||||||
|
fn from(err: lettre_email::error::Error) -> Error {
|
||||||
|
Error::LettreError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<mime::FromStrError> for Error {
|
||||||
|
fn from(err: mime::FromStrError) -> Error {
|
||||||
|
Error::FromStr(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! bail {
|
macro_rules! bail {
|
||||||
($e:expr) => {
|
($e:expr) => {
|
||||||
|
|||||||
@@ -987,7 +987,7 @@ pub fn star_msgs(context: &Context, msg_ids: &[MsgId], star: bool) -> bool {
|
|||||||
pub fn get_summarytext_by_raw(
|
pub fn get_summarytext_by_raw(
|
||||||
viewtype: Viewtype,
|
viewtype: Viewtype,
|
||||||
text: Option<impl AsRef<str>>,
|
text: Option<impl AsRef<str>>,
|
||||||
param: &mut Params,
|
param: &Params,
|
||||||
approx_characters: usize,
|
approx_characters: usize,
|
||||||
context: &Context,
|
context: &Context,
|
||||||
) -> String {
|
) -> String {
|
||||||
|
|||||||
1309
src/mimefactory.rs
1309
src/mimefactory.rs
File diff suppressed because it is too large
Load Diff
@@ -462,7 +462,7 @@ impl<'a> MimeParser<'a> {
|
|||||||
https://k9mail.github.io/2016/11/24/OpenPGP-Considerations-Part-I.html
|
https://k9mail.github.io/2016/11/24/OpenPGP-Considerations-Part-I.html
|
||||||
for background information why we use encrypted+signed) */
|
for background information why we use encrypted+signed) */
|
||||||
if let Some(first) = mail.subparts.iter().next() {
|
if let Some(first) = mail.subparts.iter().next() {
|
||||||
any_part_added = self.parse_mime_recursive(mail)?;
|
any_part_added = self.parse_mime_recursive(first)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(DC_MIMETYPE_MP_REPORT, _) => {
|
(DC_MIMETYPE_MP_REPORT, _) => {
|
||||||
@@ -802,7 +802,7 @@ impl<'a> MimeParser<'a> {
|
|||||||
// 3. retrieve information
|
// 3. retrieve information
|
||||||
|
|
||||||
// must be present
|
// must be present
|
||||||
if let Some(disposition) = report_fields.get_first_value("Disposition").ok().flatten() {
|
if let Some(_disposition) = report_fields.get_first_value("Disposition").ok().flatten() {
|
||||||
if let Some(original_message_id) = report_fields
|
if let Some(original_message_id) = report_fields
|
||||||
.get_first_value("Original-Message-ID")
|
.get_first_value("Original-Message-ID")
|
||||||
.ok()
|
.ok()
|
||||||
|
|||||||
@@ -48,16 +48,6 @@ pub fn get_autocrypt_mime<'a, 'b>(mail: &'a ParsedMail<'b>) -> Result<&'a Parsed
|
|||||||
Ok(&mail.subparts[1])
|
Ok(&mail.subparts[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_decryptable_data(mail: &ParsedMail<'_>) -> bool {
|
|
||||||
false
|
|
||||||
// /* MAILMIME_DATA_FILE indicates, the data is in a file; AFAIK this is not used on parsing */
|
|
||||||
// {
|
|
||||||
// (*mime_data).dt_type == MAILMIME_DATA_TEXT as libc::c_int
|
|
||||||
// && !(*mime_data).dt_data.dt_text.dt_data.is_null()
|
|
||||||
// && (*mime_data).dt_data.dt_text.dt_length > 0
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
// returned addresses are normalized.
|
// returned addresses are normalized.
|
||||||
pub fn mailimf_get_recipients(headers: &HashMap<String, String>) -> HashSet<String> {
|
pub fn mailimf_get_recipients(headers: &HashMap<String, String>) -> HashSet<String> {
|
||||||
let mut recipients: HashSet<String> = Default::default();
|
let mut recipients: HashSet<String> = Default::default();
|
||||||
|
|||||||
Reference in New Issue
Block a user