diff options
-rw-r--r-- | config_macros.rs | 10 | ||||
-rw-r--r-- | docs/meli.conf.5 | 5 | ||||
-rw-r--r-- | melib/src/conf.rs | 21 | ||||
-rw-r--r-- | melib/src/gpgme/bindings.rs | 54 | ||||
-rw-r--r-- | melib/src/gpgme/mod.rs | 331 | ||||
-rw-r--r-- | samples/sample-config.toml | 1 | ||||
-rw-r--r-- | src/components/mail.rs | 1 | ||||
-rw-r--r-- | src/components/mail/compose.rs | 609 | ||||
-rw-r--r-- | src/components/mail/pgp.rs | 270 | ||||
-rw-r--r-- | src/components/mail/view.rs | 262 | ||||
-rw-r--r-- | src/components/utilities/widgets.rs | 4 | ||||
-rw-r--r-- | src/conf/overrides.rs | 42 | ||||
-rw-r--r-- | src/conf/pgp.rs | 47 |
13 files changed, 1058 insertions, 599 deletions
diff --git a/config_macros.rs b/config_macros.rs index a70fd1e3..e4c3ca5c 100644 --- a/config_macros.rs +++ b/config_macros.rs @@ -87,6 +87,14 @@ use super::*; } let override_ident: syn::Ident = format_ident!("{}Override", s.ident); let mut field_tokentrees = vec![]; + let mut attrs_tokens = vec![]; + for attr in &s.attrs { + if let Ok(syn::Meta::List(ml)) = attr.parse_meta() { + if ml.path.get_ident().is_some() && ml.path.get_ident().unwrap() == "cfg" { + attrs_tokens.push(attr); + } + } + } let mut field_idents = vec![]; for f in &s.fields { let ident = &f.ident; @@ -146,6 +154,7 @@ use super::*; //let fields = &s.fields; let literal_struct = quote! { + #(#attrs_tokens)* #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(deny_unknown_fields)] pub struct #override_ident { @@ -153,6 +162,7 @@ use super::*; } + #(#attrs_tokens)* impl Default for #override_ident { fn default() -> Self { #override_ident { diff --git a/docs/meli.conf.5 b/docs/meli.conf.5 index 174bec26..f42477ff 100644 --- a/docs/meli.conf.5 +++ b/docs/meli.conf.5 @@ -892,11 +892,6 @@ Always sign sent messages Key to be used when signing/encrypting (not functional yet) .\" default value .Pq Em none -.It Ic gpg_binary Ar String -.Pq Em optional -The gpg binary name or file location to use -.\" default value -.Pq Em "gpg2" .El .Sh TERMINAL .Bl -tag -width 36n diff --git a/melib/src/conf.rs b/melib/src/conf.rs index a78cbc14..0395dd4d 100644 --- a/melib/src/conf.rs +++ b/melib/src/conf.rs @@ -128,6 +128,7 @@ pub enum ToggleFlag { InternalVal(bool), False, True, + Ask, } impl From<bool> for ToggleFlag { @@ -157,9 +158,15 @@ impl ToggleFlag { false } } + + pub fn is_ask(&self) -> bool { + *self == ToggleFlag::Ask + } + pub fn is_false(&self) -> bool { ToggleFlag::False == *self || ToggleFlag::InternalVal(false) == *self } + pub fn is_true(&self) -> bool { ToggleFlag::True == *self || ToggleFlag::InternalVal(true) == *self } @@ -174,6 +181,7 @@ impl Serialize for ToggleFlag { ToggleFlag::Unset | ToggleFlag::InternalVal(_) => serializer.serialize_none(), ToggleFlag::False => serializer.serialize_bool(false), ToggleFlag::True => serializer.serialize_bool(true), + ToggleFlag::Ask => serializer.serialize_str("ask"), } } } @@ -183,10 +191,17 @@ impl<'de> Deserialize<'de> for ToggleFlag { where D: Deserializer<'de>, { - let s = <bool>::deserialize(deserializer); + let s = <String>::deserialize(deserializer); Ok(match s? { - true => ToggleFlag::True, - false => ToggleFlag::False, + s if s.eq_ignore_ascii_case("true") => ToggleFlag::True, + s if s.eq_ignore_ascii_case("false") => ToggleFlag::False, + s if s.eq_ignore_ascii_case("ask") => ToggleFlag::Ask, + s => { + return Err(serde::de::Error::custom(format!( + r#"expected one of "true", "false", "ask", found `{}`"#, + s + ))) + } }) } } diff --git a/melib/src/gpgme/bindings.rs b/melib/src/gpgme/bindings.rs index 390e3bc7..49273015 100644 --- a/melib/src/gpgme/bindings.rs +++ b/melib/src/gpgme/bindings.rs @@ -7220,12 +7220,8 @@ extern "C" { extern "C" { pub fn gpgme_get_protocol_name(proto: gpgme_protocol_t) -> *const ::std::os::raw::c_char; } -extern "C" { - pub fn gpgme_set_armor(ctx: gpgme_ctx_t, yes: ::std::os::raw::c_int); -} -extern "C" { - pub fn gpgme_get_armor(ctx: gpgme_ctx_t) -> ::std::os::raw::c_int; -} +pub type gpgme_set_armor = unsafe extern "C" fn(ctx: gpgme_ctx_t, yes: ::std::os::raw::c_int); +pub type gpgme_get_armor = unsafe extern "C" fn(ctx: gpgme_ctx_t) -> ::std::os::raw::c_int; extern "C" { pub fn gpgme_set_textmode(ctx: gpgme_ctx_t, yes: ::std::os::raw::c_int); } @@ -7309,12 +7305,10 @@ extern "C" { home_dir: *const ::std::os::raw::c_char, ) -> gpgme_error_t; } -extern "C" { - pub fn gpgme_signers_clear(ctx: gpgme_ctx_t); -} -extern "C" { - pub fn gpgme_signers_add(ctx: gpgme_ctx_t, key: gpgme_key_t) -> gpgme_error_t; -} +pub type gpgme_signers_clear = unsafe extern "C" fn(ctx: gpgme_ctx_t); + +pub type gpgme_signers_add = + unsafe extern "C" fn(ctx: gpgme_ctx_t, key: gpgme_key_t) -> gpgme_error_t; extern "C" { pub fn gpgme_signers_count(ctx: gpgme_ctx_t) -> ::std::os::raw::c_uint; } @@ -7763,9 +7757,7 @@ fn bindgen_test_layout__gpgme_op_encrypt_result() { ); } pub type gpgme_encrypt_result_t = *mut _gpgme_op_encrypt_result; -extern "C" { - pub fn gpgme_op_encrypt_result(ctx: gpgme_ctx_t) -> gpgme_encrypt_result_t; -} +pub type gpgme_op_encrypt_result = unsafe extern "C" fn(ctx: gpgme_ctx_t) -> gpgme_encrypt_result_t; pub const gpgme_encrypt_flags_t_GPGME_ENCRYPT_ALWAYS_TRUST: gpgme_encrypt_flags_t = 1; pub const gpgme_encrypt_flags_t_GPGME_ENCRYPT_NO_ENCRYPT_TO: gpgme_encrypt_flags_t = 2; pub const gpgme_encrypt_flags_t_GPGME_ENCRYPT_PREPARE: gpgme_encrypt_flags_t = 4; @@ -7776,15 +7768,13 @@ pub const gpgme_encrypt_flags_t_GPGME_ENCRYPT_THROW_KEYIDS: gpgme_encrypt_flags_ pub const gpgme_encrypt_flags_t_GPGME_ENCRYPT_WRAP: gpgme_encrypt_flags_t = 128; pub const gpgme_encrypt_flags_t_GPGME_ENCRYPT_WANT_ADDRESS: gpgme_encrypt_flags_t = 256; pub type gpgme_encrypt_flags_t = u32; -extern "C" { - pub fn gpgme_op_encrypt_start( - ctx: gpgme_ctx_t, - recp: *mut gpgme_key_t, - flags: gpgme_encrypt_flags_t, - plain: gpgme_data_t, - cipher: gpgme_data_t, - ) -> gpgme_error_t; -} +pub type gpgme_op_encrypt_start = unsafe extern "C" fn( + ctx: gpgme_ctx_t, + recp: *mut gpgme_key_t, + flags: gpgme_encrypt_flags_t, + plain: gpgme_data_t, + cipher: gpgme_data_t, +) -> gpgme_error_t; extern "C" { pub fn gpgme_op_encrypt( ctx: gpgme_ctx_t, @@ -7814,15 +7804,13 @@ extern "C" { cipher: gpgme_data_t, ) -> gpgme_error_t; } -extern "C" { - pub fn gpgme_op_encrypt_sign_start( - ctx: gpgme_ctx_t, - recp: *mut gpgme_key_t, - flags: gpgme_encrypt_flags_t, - plain: gpgme_data_t, - cipher: gpgme_data_t, - ) -> gpgme_error_t; -} +pub type gpgme_op_encrypt_sign_start = unsafe extern "C" fn( + ctx: gpgme_ctx_t, + recp: *mut gpgme_key_t, + flags: gpgme_encrypt_flags_t, + plain: gpgme_data_t, + cipher: gpgme_data_t, +) -> gpgme_error_t; extern "C" { pub fn gpgme_op_encrypt_sign( ctx: gpgme_ctx_t, diff --git a/melib/src/gpgme/mod.rs b/melib/src/gpgme/mod.rs index e7d334d6..b0674619 100644 --- a/melib/src/gpgme/mod.rs +++ b/melib/src/gpgme/mod.rs @@ -25,11 +25,16 @@ use crate::email::{ }; use crate::error::{ErrorKind, IntoMeliError, MeliError, Result, ResultIntoMeliError}; use futures::FutureExt; +use serde::{ + de::{self, Deserialize}, + Deserializer, Serialize, Serializer, +}; use smol::Async; use std::borrow::Cow; use std::collections::HashMap; use std::ffi::{CStr, CString, OsStr}; use std::future::Future; +use std::io::Seek; use std::os::unix::ffi::OsStrExt; use std::os::unix::io::{AsRawFd, RawFd}; use std::path::Path; @@ -66,6 +71,7 @@ pub enum GpgmeFlag { ///"auto-key-retrieve" AutoKeyRetrieve, OfflineMode, + AsciiArmor, } bitflags! { @@ -91,6 +97,85 @@ bitflags! { } } +impl<'de> Deserialize<'de> for LocateKey { + fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error> + where + D: Deserializer<'de>, + { + if let Ok(s) = <String>::deserialize(deserializer) { + LocateKey::from_string_de::<'de, D, String>(s) + } else { + Err(de::Error::custom("LocateKey value must be a string.")) + } + } +} + +impl Serialize for LocateKey { + fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> + where + S: Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +impl LocateKey { + pub fn from_string_de<'de, D, T: AsRef<str>>(s: T) -> std::result::Result<Self, D::Error> + where + D: Deserializer<'de>, + { + Ok(match s.as_ref().trim() { + s if s.eq_ignore_ascii_case("cert") => LocateKey::CERT, + s if s.eq_ignore_ascii_case("pka") => LocateKey::PKA, + s if s.eq_ignore_ascii_case("dane") => LocateKey::DANE, + s if s.eq_ignore_ascii_case("wkd") => LocateKey::WKD, + s if s.eq_ignore_ascii_case("ldap") => LocateKey::LDAP, + s if s.eq_ignore_ascii_case("keyserver") => LocateKey::KEYSERVER, + s if s.eq_ignore_ascii_case("keyserver-url") => LocateKey::KEYSERVER_URL, + s if s.eq_ignore_ascii_case("local") => LocateKey::LOCAL, + combination if combination.contains(",") => { + let mut ret = LocateKey::NODEFAULT; + for c in combination.trim().split(",") { + ret |= Self::from_string_de::<'de, D, &str>(c.trim())?; + } + ret + } + _ => { + return Err(de::Error::custom( + r#"Takes valid auto-key-locate GPG values: "cert", "pka", "dane", "wkd", "ldap", "keyserver", "keyserver-URL", "local", "nodefault""#, + )) + } + }) + } +} + +impl std::fmt::Display for LocateKey { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + if *self == LocateKey::NODEFAULT { + write!(fmt, "clear,nodefault") + } else { + let mut accum = String::new(); + macro_rules! is_set { + ($flag:expr, $string:literal) => {{ + if self.intersects($flag) { + accum.push_str($string); + accum.push_str(","); + } + }}; + } + is_set!(LocateKey::CERT, "cert"); + is_set!(LocateKey::PKA, "pka"); + is_set!(LocateKey::WKD, "wkd"); + is_set!(LocateKey::LDAP, "ldap"); + is_set!(LocateKey::KEYSERVER, "keyserver"); + is_set!(LocateKey::KEYSERVER_URL, "keyserver-url"); + is_set!(LocateKey::LOCAL, "local"); + accum.pop(); + write!(fmt, "{}", accum) + } + } +} + struct IoState { max_idx: usize, ops: HashMap<usize, GpgmeFd>, @@ -187,6 +272,7 @@ impl Context { }; ret.set_flag(GpgmeFlag::AutoKeyRetrieve, false)?; ret.set_flag(GpgmeFlag::OfflineMode, true)?; + ret.set_flag(GpgmeFlag::AsciiArmor, true)?; ret.set_auto_key_locate(LocateKey::LOCAL)?; Ok(ret) } @@ -221,12 +307,21 @@ impl Context { }; return Ok(()); } + GpgmeFlag::AsciiArmor => { + unsafe { + call!(&self.inner.lib, gpgme_set_armor)( + self.inner.inner.as_ptr(), + if value { 1 } else { 0 }, + ); + }; + return Ok(()); + } }; const VALUE_ON: &[u8; 2] = b"1\0"; const VALUE_OFF: &[u8; 2] = b"0\0"; let raw_flag = match flag { GpgmeFlag::AutoKeyRetrieve => c_string_literal!("auto-key-retrieve"), - GpgmeFlag::OfflineMode => unreachable!(), + GpgmeFlag::AsciiArmor | GpgmeFlag::OfflineMode => unreachable!(), }; self.set_flag_inner( raw_flag, @@ -249,6 +344,11 @@ impl Context { call!(&self.inner.lib, gpgme_get_offline)(self.inner.inner.as_ptr()) > 0 }); } + GpgmeFlag::AsciiArmor => { + return Ok(unsafe { + call!(&self.inner.lib, gpgme_get_armor)(self.inner.inner.as_ptr()) > 0 + }); + } }; let val = self.get_flag_inner(raw_flag); Ok(!val.is_null()) @@ -259,23 +359,7 @@ impl Context { if val == LocateKey::NODEFAULT { self.set_flag_inner(auto_key_locate, c_string_literal!("clear,nodefault")) } else { - let mut accum = String::new(); - macro_rules! is_set { - ($flag:expr, $string:literal) => {{ - if val.intersects($flag) { - accum.push_str($string); - accum.push_str(","); - } - }}; - } - is_set!(LocateKey::CERT, "cert"); - is_set!(LocateKey::PKA, "pka"); - is_set!(LocateKey::WKD, "wkd"); - is_set!(LocateKey::LDAP, "ldap"); - is_set!(LocateKey::KEYSERVER, "keyserver"); - is_set!(LocateKey::KEYSERVER_URL, "keyserver-url"); - is_set!(LocateKey::LOCAL, "local"); - accum.pop(); + let mut accum = val.to_string(); accum.push('\0'); self.set_flag_inner( auto_key_locate, @@ -592,11 +676,34 @@ impl Context { }) } - pub fn sign<'d>( + pub fn sign( &mut self, - text: &'d mut Data, - ) -> Result<impl Future<Output = Result<()>> + 'd> { - let sig = std::ptr::null_mut(); + sign_keys: Vec<Key>, + mut text: Data, + ) -> Result<impl Future<Output = Result<Vec<u8>>>> { + if sign_keys.is_empty() { + return Err( + MeliError::new("gpgme: Call to sign() with zero keys.").set_kind(ErrorKind::Bug) + ); + } + let mut sig: gpgme_data_t = std::ptr::null_mut(); + unsafe { + gpgme_error_try( + &self.inner.lib, + call!(&self.inner.lib, gpgme_data_new)(&mut sig), + )?; + call!(&self.inner.lib, gpgme_signers_clear)(self.inner.inner.as_ptr()); + for k in sign_keys { + gpgme_error_try( + &self.inner.lib, + call!(&self.inner.lib, gpgme_signers_add)( + self.inner.inner.as_ptr(), + k.inner.inner.as_ptr(), + ), + )?; + } + } + unsafe { gpgme_error_try( &self.inner.lib, @@ -608,6 +715,13 @@ impl Context { ), )?; } + let mut sig = Data { + lib: self.inner.lib.clone(), + kind: DataKind::Memory, + inner: core::ptr::NonNull::new(sig).ok_or_else(|| { + MeliError::new("internal libgpgme error").set_kind(ErrorKind::Bug) + })?, + }; let io_state = self.io_state.clone(); let io_state_lck = self.io_state.lock().unwrap(); @@ -672,13 +786,17 @@ impl Context { }; let _ = rcv.recv().await; let io_state_lck = io_state.lock().unwrap(); - let ret = io_state_lck + io_state_lck .done .lock() .unwrap() .take() - .unwrap_or_else(|| Err(MeliError::new("Unspecified libgpgme error"))); - ret + .unwrap_or_else(|| Err(MeliError::new("Unspecified libgpgme error")))?; + sig.seek(std::io::SeekFrom::Start(0)) + .chain_err_summary(|| { + "libgpgme error: could not perform seek on signature data object" + })?; + Ok(sig.into_bytes()?) }) } @@ -830,7 +948,6 @@ impl Context { recipient_iter = (*recipient_iter).next; } } - use std::io::Seek; /* Rewind cursor */ plain .seek(std::io::SeekFrom::Start(0)) @@ -846,6 +963,168 @@ impl Context { )) }) } + + pub fn encrypt( + &mut self, + sign_keys: Option<Vec<Key>>, + encrypt_keys: Vec<Key>, + mut plain: Data, + ) -> Result<impl Future<Output = Result<Vec<u8>>> + Send> { + if encrypt_keys.is_empty() { + return Err( + MeliError::new("gpgme: Call to encrypt() with zero keys.").set_kind(ErrorKind::Bug) + ); + } + unsafe { + call!(&self.inner.lib, gpgme_signers_clear)(self.inner.inner.as_ptr()); + } + + let also_sign: bool = if let Some(keys) = sign_keys { + if keys.is_empty() { + false + } else { + for k in keys { + unsafe { + gpgme_error_try( + &self.inner.lib, + call!(&self.inner.lib, gpgme_signers_add)( + self.inner.inner.as_ptr(), + k.inner.inner.as_ptr(), + ), + )?; + } + } + true + } + } else { + false + }; + let mut cipher: gpgme_data_t = std::ptr::null_mut(); + let mut raw_keys: Vec<gpgme_key_t> = Vec::with_capacity(encrypt_keys.len() + 1); + raw_keys.extend(encrypt_keys.iter().map(|k| k.inner.inner.as_ptr())); + raw_keys.push(std::ptr::null_mut()); + unsafe { + gpgme_error_try( + &self.inner.lib, + call!(&self.inner.lib, gpgme_data_new)(&mut cipher), + )?; + gpgme_error_try( + &self.inner.lib, + if also_sign { + call!(&self.inner.lib, gpgme_op_encrypt_sign_start)( + self.inner.inner.as_ptr(), + raw_keys.as_mut_ptr(), + gpgme_encrypt_flags_t_GPGME_ENCRYPT_NO_ENCRYPT_TO + | gpgme_encrypt_flags_t_GPGME_ENCRYPT_NO_COMPRESS, + plain.inner.as_mut(), + cipher, + ) + } else { + call!(&self.inner.lib, gpgme_op_encrypt_start)( + self.inner.inner.as_ptr(), + raw_keys.as_mut_ptr(), + gpgme_encrypt_flags_t_GPGME_ENCRYPT_NO_ENCRYPT_TO + | gpgme_encrypt_flags_t_GPGME_ENCRYPT_NO_COMPRESS, + plain.inner.as_mut(), + cipher, + ) + }, + )?; + } + let mut cipher = Data { + lib: self.inner.lib.clone(), + kind: DataKind::Memory, + inner: core::ptr::NonNull::new(cipher).ok_or_else(|| { + MeliError::new("internal libgpgme error").set_kind(ErrorKind::Bug) + })?, + }; + + let ctx = self.inner.clone(); + let io_state = self.io_state.clone(); + let io_state_lck = self.io_state.lock().unwrap(); + let done = io_state_lck.done.clone(); + let fut = io_state_lck + .ops + .values() + .map(|a| Async::new(a.clone()).unwrap()) + .collect::<Vec<Async<GpgmeFd>>>(); + drop(io_state_lck); + Ok(async move { + futures::future::join_all(fut.iter().map(|fut| { + let done = done.clone(); + if fut.get_ref().write { + futures::future::select( + fut.get_ref().receiver.recv().boxed(), + fut.write_with(move |_f| { + if done.lock().unwrap().is_some() { + return Ok(()); + } + unsafe { + (fut.get_ref().fnc.unwrap())( + fut.get_ref().fnc_data, + fut.get_ref().fd, + ) + }; + if done.lock().unwrap().is_none() { + return Err(std::io::ErrorKind::WouldBlock.into()); + } + Ok(()) + }) + .boxed(), + ) + .boxed() + } else { + futures::future::select( + fut.get_ref().receiver.recv().boxed(), + fut.read_with(move |_f| { + if done.lock().unwrap().is_some() { + return Ok(()); + } + unsafe { + (fut.get_ref().fnc.unwrap())( + fut.get_ref().fnc_data, + fut.get_ref().fd, + ) + }; + if done.lock().unwrap().is_none() { + return Err(std::io::ErrorKind::WouldBlock.into()); + } + Ok(()) + }) + .boxed(), + ) + .boxed() + } + })) + .await; + let rcv = { + let io_state_lck = io_state.lock().unwrap(); + io_state_lck.receiver.clone() + }; + let _ = rcv.recv().await; + let io_state_lck = io_state.lock().unwrap(); + io_state_lck + .done + .lock() + .unwrap() + .take() + .unwrap_or_else(|| Err(MeliError::new("Unspecified libgpgme error")))?; + + let encrypt_result = + unsafe { call!(&ctx.lib, gpgme_op_encrypt_result)(ctx.inner.as_ptr()) }; + if encrypt_result.is_null() { + return Err(MeliError::new( + "Unspecified libgpgme error: gpgme_op_encrypt_result returned NULL.", + ) + .set_err_kind(ErrorKind::External)); + } + /* Rewind cursor */ + cipher + .seek(std::io::SeekFrom::Start(0)) + .chain_err_summary(|| "libgpgme error: could not perform seek on plain text")?; + Ok(cipher.into_bytes()?) + }) + } } fn gpgme_error_try(lib: &libloading::Library, error_code: GpgmeError) -> Result<()> { diff --git a/samples/sample-config.toml b/samples/sample-config.toml index fad4bd15..367310a7 100644 --- a/samples/sample-config.toml +++ b/samples/sample-config.toml @@ -112,7 +112,6 @@ #[pgp] #auto_sign = false # always sign sent messages #auto_verify_signatures = true # always verify signatures when reading signed e-mails -#gpg_binary = "/usr/bin/gpg2" #optional # #[terminal] #theme = "dark" # or "light" diff --git a/src/components/mail.rs b/src/components/mail.rs index 0aa19489..cb9be3d2 100644 --- a/src/components/mail.rs +++ b/src/components/mail.rs @@ -33,6 +33,7 @@ pub use crate::view::*; mod compose; pub use self::compose::*; +#[cfg(feature = "gpgme")] pub mod pgp; mod status; diff --git a/src/components/mail/compose.rs b/src/components/mail/compose.rs index c5f3e53f..31cbf5b5 100644 --- a/src/components/mail/compose.rs +++ b/src/components/mail/compose.rs @@ -84,8 +84,8 @@ pub struct Composer { embed_area: Area, embed: Option<EmbedStatus>, - sign_mail: ToggleFlag, - encrypt_mail: ToggleFlag, + #[cfg(feature = "gpgme")] + gpg_state: gpg::GpgComposeState, dirty: bool, has_changes: bool, initialized: bool, @@ -107,8 +107,8 @@ impl Default for Composer { form: FormWidget::default(), mode: ViewMode::Edit, - sign_mail: ToggleFlag::Unset, - encrypt_mail: ToggleFlag::Unset, + #[cfg(feature = "gpgme")] + gpg_state: gpg::GpgComposeState::new(), dirty: true, has_changes: false, embed_area: ((0, 0), (0, 0)), @@ -125,6 +125,8 @@ enum ViewMode { Edit, Embed, SelectRecipients(UIDialog<Address>), + #[cfg(feature = "gpgme")] + SelectEncryptKey(bool, gpg::KeySelection), Send(UIConfirmationDialog), WaitingForSendResult(UIDialog<char>, JoinHandle<Result<()>>), } @@ -430,14 +432,23 @@ impl Composer { let attachments_no = self.draft.attachments().len(); let theme_default = crate::conf::value(context, "theme_default"); clear_area(grid, area, theme_default); - if self.sign_mail.is_true() { + #[cfg(feature = "gpgme")] + if self.gpg_state.sign_mail.is_true() { + let key_list = self + .gpg_state + .sign_keys + .iter() + .map(|k| k.fingerprint()) + .collect::<Vec<_>>() + .join(", "); write_string_to_grid( &format!( "☑ sign with {}", - account_settings!(context[self.account_hash].pgp.sign_key) - .as_ref() - .map(|s| s.as_str()) - .unwrap_or("default key") + if self.gpg_state.sign_keys.is_empty() { + "default key" + } else { + key_list.as_str() + } ), grid, theme_default.fg, @@ -465,14 +476,29 @@ impl Composer { None, ); } - if self.encrypt_mail.is_true() { + #[cfg(feature = "gpgme")] + if self.gpg_state.encrypt_mail.is_true() { + let key_list = self + .gpg_state + .encrypt_keys + .iter() + .map(|k| k.fingerprint()) + .collect::<Vec<_>>() + .join(", "); + 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") + "{}{}", + if self.gpg_state.encrypt_keys.is_empty() { + "☐ no keys to encrypt with!" + } else { + "☑ encrypt with " + }, + if self.gpg_state.encrypt_keys.is_empty() { + "" + } else { + key_list.as_str() + } ), grid, theme_default.fg, @@ -575,8 +601,9 @@ impl Component for Composer { let width = width!(area); if !self.initialized { - if self.sign_mail.is_unset() { - self.sign_mail = ToggleFlag::InternalVal(*account_settings!( + #[cfg(feature = "gpgme")] + if self.gpg_state.sign_mail.is_unset() { + self.gpg_state.sign_mail = ToggleFlag::InternalVal(*account_settings!( context[self.account_hash].pgp.auto_sign )); } @@ -752,6 +779,18 @@ impl Component for Composer { ViewMode::Send(ref mut s) => { s.draw(grid, center_area(area, s.content.size()), context); } + #[cfg(feature = "gpgme")] + ViewMode::SelectEncryptKey( + _, + gpg::KeySelection::Loaded { + ref mut widget, + keys: _, + }, + ) => { + widget.draw(grid, center_area(area, widget.content.size()), context); + } + #[cfg(feature = "gpgme")] + ViewMode::SelectEncryptKey(_, _) => {} ViewMode::SelectRecipients(ref mut s) => { s.draw(grid, center_area(area, s.content.size()), context); } @@ -783,8 +822,8 @@ impl Component for Composer { if let Some(true) = result.downcast_ref::<bool>() { self.update_draft(); match send_draft_async( - self.sign_mail, - self.encrypt_mail, + #[cfg(feature = "gpgme")] + self.gpg_state.clone(), context, self.account_hash, self.draft.clone(), @@ -846,6 +885,14 @@ impl Component for Composer { self.mode = ViewMode::Edit; self.set_dirty(true); } + #[cfg(feature = "gpgme")] + (ViewMode::SelectEncryptKey(_, ref mut selector), UIEvent::ComponentKill(ref id)) + if *id == selector.id() => + { + self.mode = ViewMode::Edit; + self.set_dirty(true); + return true; + } (ViewMode::Send(ref mut selector), _) => { if selector.process_event(event, context) { return true; @@ -954,6 +1001,34 @@ impl Component for Composer { return true; } } + #[cfg(feature = "gpgme")] + ( + ViewMode::SelectEncryptKey(is_encrypt, ref mut selector), + UIEvent::FinishedUIDialog(id, result), + ) if *id == selector.id() => { + debug!(&result); + if let Some(key) = result.downcast_mut::<Option<melib::gpgme::Key>>() { + debug!("got key {:?}", key); + if let Some(key) = key { + if *is_encrypt { + self.gpg_state.encrypt_keys.clear(); + self.gpg_state.encrypt_keys.push(key.clone()); + } else { + self.gpg_state.sign_keys.clear(); + self.gpg_state.sign_keys.push(key.clone()); + } + } + } + self.mode = ViewMode::Edit; + self.set_dirty(true); + return true; + } + #[cfg(feature = "gpgme")] + (ViewMode::SelectEncryptKey(_, ref mut selector), _) => { + if selector.process_event(event, context) { + return true; + } + } _ => {} } if self.cursor == Cursor::Headers @@ -1025,14 +1100,15 @@ impl Component for Composer { if self.mode.is_edit() && (self.cursor == Cursor::Sign || self.cursor == Cursor::Encrypt) => { + #[cfg(feature = "gpgme")] match self.cursor { Cursor::Sign => { - let is_true = self.sign_mail.is_true(); - self.sign_mail = ToggleFlag::from(!is_true); + let is_true = self.gpg_state.sign_mail.is_true(); + self.gpg_state.sign_mail = ToggleFlag::from(!is_true); } Cursor::Encrypt => { - let is_true = self.encrypt_mail.is_true(); - self.encrypt_mail = ToggleFlag::from(!is_true); + let is_true = self.gpg_state.encrypt_mail.is_true(); + self.gpg_state.encrypt_mail = ToggleFlag::from(!is_true); } _ => {} }; @@ -1195,6 +1271,86 @@ impl Component for Composer { return true; } UIEvent::Input(ref key) + if self.mode.is_edit() + && self.cursor == Cursor::Sign + && shortcut!(key == shortcuts[Self::DESCRIPTION]["edit_mail"]) => + { + #[cfg(feature = "gpgme")] + match melib::email::parser::address::rfc2822address_list( + self.form.values()["From"].as_str().as_bytes(), + ) + .map_err(|_err| -> MeliError { "No valid sender address in `From:`".into() }) + .and_then(|(_, list)| { + list.get(0) + .cloned() + .ok_or_else(|| "No valid sender address in `From:`".into()) + }) + .and_then(|addr| { + gpg::KeySelection::new( + false, + account_settings!(context[self.account_hash].pgp.allow_remote_lookup) + .is_true(), + addr.get_email(), + *account_settings!(context[self.account_hash].pgp.allow_remote_lookup), + context, + ) + }) { + Ok(widget) => { + self.gpg_state.sign_mail = ToggleFlag::from(true); + self.mode = ViewMode::SelectEncryptKey(false, widget); + } + Err(err) => { + context.replies.push_back(UIEvent::Notification( + Some("Could not list keys.".to_string()), + format!("libgpgme error: {}", &err), |