From be45b0c02d3a7dbaec6e0ac15094e0448f5e5e29 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Fri, 9 Oct 2020 11:58:18 +0300 Subject: [PATCH] compose: add encrypt layer --- src/components/mail/compose.rs | 74 +++++++++++++++++++++++++++++---- src/components/mail/pgp.rs | 76 ++++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+), 9 deletions(-) diff --git a/src/components/mail/compose.rs b/src/components/mail/compose.rs index 463490be..93eecae0 100644 --- a/src/components/mail/compose.rs +++ b/src/components/mail/compose.rs @@ -82,6 +82,7 @@ pub struct Composer { embed_area: Area, embed: Option, sign_mail: ToggleFlag, + encrypt_mail: ToggleFlag, dirty: bool, has_changes: bool, initialized: bool, @@ -104,6 +105,7 @@ impl Default for Composer { mode: ViewMode::Edit, sign_mail: ToggleFlag::Unset, + encrypt_mail: ToggleFlag::Unset, dirty: true, has_changes: false, embed_area: ((0, 0), (0, 0)), @@ -452,15 +454,33 @@ impl Composer { None, ); } - write_string_to_grid( - "☐ don't encrypt", - grid, - theme_default.fg, - theme_default.bg, - theme_default.attrs, - (pos_inc(upper_left!(area), (0, 2)), bottom_right!(area)), - None, - ); + if self.encrypt_mail.is_true() { + write_string_to_grid( + &format!( + "☑ encrypt with {}", + account_settings!(context[self.account_hash].pgp.encrypt_key) + .as_ref() + .map(|s| s.as_str()) + .unwrap_or("default key") + ), + grid, + theme_default.fg, + theme_default.bg, + theme_default.attrs, + (pos_inc(upper_left!(area), (0, 2)), bottom_right!(area)), + None, + ); + } else { + write_string_to_grid( + "☐ don't encrypt", + grid, + theme_default.fg, + theme_default.bg, + theme_default.attrs, + (pos_inc(upper_left!(area), (0, 2)), bottom_right!(area)), + None, + ); + } if attachments_no == 0 { write_string_to_grid( "no attachments", @@ -533,6 +553,11 @@ impl Component for Composer { context[self.account_hash].pgp.auto_sign )); } + if self.encrypt_mail.is_unset() { + self.encrypt_mail = ToggleFlag::InternalVal(*account_settings!( + context[self.account_hash].pgp.auto_encrypt + )); + } if !self.draft.headers().contains_key("From") || self.draft.headers()["From"].is_empty() { self.draft.set_header( @@ -730,6 +755,7 @@ impl Component for Composer { self.update_draft(); match send_draft_async( self.sign_mail, + self.encrypt_mail, context, self.account_hash, self.draft.clone(), @@ -1324,6 +1350,9 @@ impl Component for Composer { return true; } Action::Compose(ComposeAction::ToggleEncrypt) => { + let is_true = self.encrypt_mail.is_true(); + self.encrypt_mail = ToggleFlag::from(!is_true); + self.dirty = true; return true; } _ => {} @@ -1567,6 +1596,7 @@ pub fn save_draft( pub fn send_draft_async( sign_mail: ToggleFlag, + encrypt_mail: ToggleFlag, context: &mut Context, account_hash: AccountHash, mut draft: Draft, @@ -1594,6 +1624,32 @@ pub fn send_draft_async( .map(|s| s.to_string()), )?)); } + if encrypt_mail.is_true() { + let mut recipients = vec![]; + if let Ok((_, v)) = + melib::email::parser::address::rfc2822address_list(draft.headers()["To"].as_bytes()) + { + for addr in v { + recipients.push(addr.get_email()); + } + } + if let Ok((_, v)) = + melib::email::parser::address::rfc2822address_list(draft.headers()["Cc"].as_bytes()) + { + for addr in v { + recipients.push(addr.get_email()); + } + } + filters_stack.push(Box::new(crate::components::mail::pgp::encrypt_filter( + account_settings!(context[account_hash].pgp.gpg_binary) + .as_ref() + .map(|s| s.to_string()), + account_settings!(context[account_hash].pgp.encrypt_key) + .as_ref() + .map(|s| s.to_string()), + recipients, + )?)); + } let send_mail = account_settings!(context[account_hash].composing.send_mail).clone(); let send_cb = context.accounts[&account_hash].send_async(send_mail); let mut content_type = ContentType::default(); diff --git a/src/components/mail/pgp.rs b/src/components/mail/pgp.rs index e0e40895..64c13c16 100644 --- a/src/components/mail/pgp.rs +++ b/src/components/mail/pgp.rs @@ -245,3 +245,79 @@ pub fn sign_filter( }, ) } + +pub fn encrypt_filter( + gpg_binary: Option, + my_public_key: Option, + recipients: Vec, +) -> Result< + impl FnOnce(AttachmentBuilder) -> Pin> + Send>> + + Send, +> { + let binary = gpg_binary.unwrap_or("gpg2".to_string()); + let mut command = Command::new(&binary); + command.args(&[ + "--batch", + "--no-tty", + "--encrypt", + "--armor", + "--output", + "-", + ]); + if let Some(key) = my_public_key.as_ref() { + command.args(&["--recipient", key]); + } else { + command.arg("--default-recipient-self"); + } + for r in &recipients { + command.args(&["--recipient", r.as_str()]); + } + Ok( + move |a: AttachmentBuilder| -> Pin>+Send>> { + Box::pin(async move { + let a: Attachment = a.into(); + + let sig_attachment = command + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::null()) + .spawn() + .and_then(|mut gpg| { + gpg.stdin + .as_mut() + .expect("Could not get gpg stdin") + .write_all(&melib_pgp::convert_attachment_to_rfc_spec( + a.into_raw().as_bytes(), + ))?; + let gpg = gpg.wait_with_output()?; + let mut a = Attachment::new( + ContentType::OctetStream { name: None }, + Default::default(), + gpg.stdout, + ); + a.content_disposition = ContentDisposition::from(r#"attachment; filename="msg.asc""#.as_bytes()); + Ok(a) + }) + .chain_err_summary(|| { + format!("Failed to launch {} to verify PGP signature", binary) + })?; + + let mut a: AttachmentBuilder = AttachmentBuilder::new("Version: 1".as_bytes()); + a.set_content_type_from_bytes("application/pgp-encrypted".as_bytes()); + a.set_content_disposition(ContentDisposition::from("attachment".as_bytes())); + let parts = vec![a, sig_attachment.into()]; + let boundary = ContentType::make_boundary(&parts); + Ok(Attachment::new( + ContentType::Multipart { + boundary: boundary.into_bytes(), + kind: MultipartType::Encrypted, + parts: parts.into_iter().map(|a| a.into()).collect::>(), + }, + Default::default(), + Vec::new(), + ) + .into()) + }) + }, + ) +}