add libgpgme feature
parent
b9c07bacef
commit
23ca41e3e8
|
@ -72,7 +72,7 @@ debug = false
|
||||||
members = ["melib", "tools", ]
|
members = ["melib", "tools", ]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["sqlite3", "notmuch", "regexp", "smtp", "dbus-notifications"]
|
default = ["sqlite3", "notmuch", "regexp", "smtp", "dbus-notifications", "gpgme"]
|
||||||
notmuch = ["melib/notmuch_backend", ]
|
notmuch = ["melib/notmuch_backend", ]
|
||||||
jmap = ["melib/jmap_backend",]
|
jmap = ["melib/jmap_backend",]
|
||||||
sqlite3 = ["melib/sqlite3"]
|
sqlite3 = ["melib/sqlite3"]
|
||||||
|
@ -81,6 +81,7 @@ regexp = ["pcre2"]
|
||||||
dbus-notifications = ["notify-rust",]
|
dbus-notifications = ["notify-rust",]
|
||||||
cli-docs = []
|
cli-docs = []
|
||||||
svgscreenshot = ["svg_crate"]
|
svgscreenshot = ["svg_crate"]
|
||||||
|
gpgme = ["melib/gpgme"]
|
||||||
|
|
||||||
# Print tracing logs as meli runs in stderr
|
# Print tracing logs as meli runs in stderr
|
||||||
# enable for debug tracing logs: build with --features=debug-tracing
|
# enable for debug tracing logs: build with --features=debug-tracing
|
||||||
|
|
|
@ -56,9 +56,9 @@ default = ["unicode_algorithms", "imap_backend", "maildir_backend", "mbox_backen
|
||||||
|
|
||||||
debug-tracing = []
|
debug-tracing = []
|
||||||
deflate_compression = ["flate2", ]
|
deflate_compression = ["flate2", ]
|
||||||
|
gpgme = []
|
||||||
http = ["isahc"]
|
http = ["isahc"]
|
||||||
http-static = ["isahc", "isahc/static-curl"]
|
http-static = ["isahc", "isahc/static-curl"]
|
||||||
tls = ["native-tls"]
|
|
||||||
imap_backend = ["tls"]
|
imap_backend = ["tls"]
|
||||||
jmap_backend = ["http", "serde_json"]
|
jmap_backend = ["http", "serde_json"]
|
||||||
maildir_backend = ["notify", "memmap"]
|
maildir_backend = ["notify", "memmap"]
|
||||||
|
@ -66,5 +66,6 @@ mbox_backend = ["notify", "memmap"]
|
||||||
notmuch_backend = []
|
notmuch_backend = []
|
||||||
smtp = ["tls", "base64"]
|
smtp = ["tls", "base64"]
|
||||||
sqlite3 = ["rusqlite", ]
|
sqlite3 = ["rusqlite", ]
|
||||||
|
tls = ["native-tls"]
|
||||||
unicode_algorithms = ["unicode-segmentation"]
|
unicode_algorithms = ["unicode-segmentation"]
|
||||||
vcard = []
|
vcard = []
|
||||||
|
|
|
@ -97,7 +97,7 @@ pub mod headers;
|
||||||
pub mod list_management;
|
pub mod list_management;
|
||||||
pub mod mailto;
|
pub mod mailto;
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
pub mod signatures;
|
pub mod pgp;
|
||||||
|
|
||||||
pub use address::{Address, MessageID, References, StrBuild, StrBuilder};
|
pub use address::{Address, MessageID, References, StrBuild, StrBuilder};
|
||||||
pub use attachments::{Attachment, AttachmentBuilder};
|
pub use attachments::{Attachment, AttachmentBuilder};
|
||||||
|
@ -219,6 +219,7 @@ impl core::fmt::Debug for Envelope {
|
||||||
.field("Message-ID", &self.message_id_display())
|
.field("Message-ID", &self.message_id_display())
|
||||||
.field("In-Reply-To", &self.in_reply_to_display())
|
.field("In-Reply-To", &self.in_reply_to_display())
|
||||||
.field("References", &self.references)
|
.field("References", &self.references)
|
||||||
|
.field("Flags", &self.flags)
|
||||||
.field("Hash", &self.hash)
|
.field("Hash", &self.hash)
|
||||||
.finish()
|
.finish()
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,6 +135,7 @@ impl Display for Charset {
|
||||||
pub enum MultipartType {
|
pub enum MultipartType {
|
||||||
Alternative,
|
Alternative,
|
||||||
Digest,
|
Digest,
|
||||||
|
Encrypted,
|
||||||
Mixed,
|
Mixed,
|
||||||
Related,
|
Related,
|
||||||
Signed,
|
Signed,
|
||||||
|
@ -148,13 +149,18 @@ impl Default for MultipartType {
|
||||||
|
|
||||||
impl Display for MultipartType {
|
impl Display for MultipartType {
|
||||||
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
||||||
match self {
|
write!(
|
||||||
MultipartType::Alternative => write!(f, "multipart/alternative"),
|
f,
|
||||||
MultipartType::Digest => write!(f, "multipart/digest"),
|
"{}",
|
||||||
MultipartType::Mixed => write!(f, "multipart/mixed"),
|
match self {
|
||||||
MultipartType::Related => write!(f, "multipart/related"),
|
MultipartType::Alternative => "multipart/alternative",
|
||||||
MultipartType::Signed => write!(f, "multipart/signed"),
|
MultipartType::Digest => "multipart/digest",
|
||||||
}
|
MultipartType::Encrypted => "multipart/encrypted",
|
||||||
|
MultipartType::Mixed => "multipart/mixed",
|
||||||
|
MultipartType::Related => "multipart/related",
|
||||||
|
MultipartType::Signed => "multipart/signed",
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,6 +172,8 @@ impl From<&[u8]> for MultipartType {
|
||||||
MultipartType::Alternative
|
MultipartType::Alternative
|
||||||
} else if val.eq_ignore_ascii_case(b"digest") {
|
} else if val.eq_ignore_ascii_case(b"digest") {
|
||||||
MultipartType::Digest
|
MultipartType::Digest
|
||||||
|
} else if val.eq_ignore_ascii_case(b"encrypted") {
|
||||||
|
MultipartType::Encrypted
|
||||||
} else if val.eq_ignore_ascii_case(b"signed") {
|
} else if val.eq_ignore_ascii_case(b"signed") {
|
||||||
MultipartType::Signed
|
MultipartType::Signed
|
||||||
} else if val.eq_ignore_ascii_case(b"related") {
|
} else if val.eq_ignore_ascii_case(b"related") {
|
||||||
|
@ -209,6 +217,74 @@ impl Default for ContentType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PartialEq<&str> for ContentType {
|
||||||
|
fn eq(&self, other: &&str) -> bool {
|
||||||
|
match (self, *other) {
|
||||||
|
(
|
||||||
|
ContentType::Text {
|
||||||
|
kind: Text::Plain, ..
|
||||||
|
},
|
||||||
|
"text/plain",
|
||||||
|
) => true,
|
||||||
|
(
|
||||||
|
ContentType::Text {
|
||||||
|
kind: Text::Html, ..
|
||||||
|
},
|
||||||
|
"text/html",
|
||||||
|
) => true,
|
||||||
|
(
|
||||||
|
ContentType::Multipart {
|
||||||
|
kind: MultipartType::Alternative,
|
||||||
|
..
|
||||||
|
},
|
||||||
|
"multipart/alternative",
|
||||||
|
) => true,
|
||||||
|
(
|
||||||
|
ContentType::Multipart {
|
||||||
|
kind: MultipartType::Digest,
|
||||||
|
..
|
||||||
|
},
|
||||||
|
"multipart/digest",
|
||||||
|
) => true,
|
||||||
|
(
|
||||||
|
ContentType::Multipart {
|
||||||
|
kind: MultipartType::Encrypted,
|
||||||
|
..
|
||||||
|
},
|
||||||
|
"multipart/encrypted",
|
||||||
|
) => true,
|
||||||
|
(
|
||||||
|
ContentType::Multipart {
|
||||||
|
kind: MultipartType::Mixed,
|
||||||
|
..
|
||||||
|
},
|
||||||
|
"multipart/mixed",
|
||||||
|
) => true,
|
||||||
|
(
|
||||||
|
ContentType::Multipart {
|
||||||
|
kind: MultipartType::Related,
|
||||||
|
..
|
||||||
|
},
|
||||||
|
"multipart/related",
|
||||||
|
) => true,
|
||||||
|
(
|
||||||
|
ContentType::Multipart {
|
||||||
|
kind: MultipartType::Signed,
|
||||||
|
..
|
||||||
|
},
|
||||||
|
"multipart/signed",
|
||||||
|
) => true,
|
||||||
|
(ContentType::PGPSignature, "application/pgp-signature") => true,
|
||||||
|
(ContentType::MessageRfc822, "message/rfc822") => true,
|
||||||
|
(ContentType::Other { tag, .. }, _) => {
|
||||||
|
other.eq_ignore_ascii_case(&String::from_utf8_lossy(&tag))
|
||||||
|
}
|
||||||
|
(ContentType::OctetStream { .. }, "application/octet-stream") => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Display for ContentType {
|
impl Display for ContentType {
|
||||||
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
||||||
match self {
|
match self {
|
||||||
|
|
|
@ -351,16 +351,14 @@ pub struct Attachment {
|
||||||
|
|
||||||
impl fmt::Debug for Attachment {
|
impl fmt::Debug for Attachment {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "Attachment {{\n content_type: {:?},\n content_transfer_encoding: {:?},\n raw: Vec of {} bytes\n, body:\n{}\n}}",
|
let mut text = Vec::with_capacity(4096);
|
||||||
self.content_type,
|
self.get_text_recursive(&mut text);
|
||||||
self.content_transfer_encoding,
|
f.debug_struct("Attachment")
|
||||||
self.raw.len(),
|
.field("content_type", &self.content_type)
|
||||||
{
|
.field("content_transfer_encoding", &self.content_transfer_encoding)
|
||||||
let mut text = Vec::with_capacity(4096);
|
.field("raw bytes length", &self.raw.len())
|
||||||
self.get_text_recursive(&mut text);
|
.field("body", &String::from_utf8_lossy(&text))
|
||||||
std::str::from_utf8(&text).map(std::string::ToString::to_string).unwrap_or_else(|e| format!("Unicode error {}", e))
|
.finish()
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -371,44 +369,63 @@ impl fmt::Display for Attachment {
|
||||||
match Mail::new(self.body.display_bytes(&self.raw).to_vec(), None) {
|
match Mail::new(self.body.display_bytes(&self.raw).to_vec(), None) {
|
||||||
Ok(wrapper) => write!(
|
Ok(wrapper) => write!(
|
||||||
f,
|
f,
|
||||||
"message/rfc822: {} - {} - {}",
|
"{} - {} - {} [message/rfc822] {}",
|
||||||
wrapper.date(),
|
wrapper.date(),
|
||||||
wrapper.field_from_to_string(),
|
wrapper.field_from_to_string(),
|
||||||
wrapper.subject()
|
wrapper.subject(),
|
||||||
|
crate::Bytes(self.raw.len()),
|
||||||
|
),
|
||||||
|
Err(err) => write!(
|
||||||
|
f,
|
||||||
|
"could not parse: {} [message/rfc822] {}",
|
||||||
|
err,
|
||||||
|
crate::Bytes(self.raw.len()),
|
||||||
),
|
),
|
||||||
Err(e) => write!(f, "{}", e),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ContentType::PGPSignature => write!(f, "pgp signature {}", self.mime_type()),
|
ContentType::PGPSignature => write!(f, "pgp signature [{}]", self.mime_type()),
|
||||||
ContentType::OctetStream { ref name } => {
|
ContentType::OctetStream { .. } | ContentType::Other { .. } => {
|
||||||
write!(f, "{}", name.clone().unwrap_or_else(|| self.mime_type()))
|
if let Some(name) = self.filename() {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"\"{}\", [{}] {}",
|
||||||
|
name,
|
||||||
|
self.mime_type(),
|
||||||
|
crate::Bytes(self.raw.len())
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"Data attachment [{}] {}",
|
||||||
|
self.mime_type(),
|
||||||
|
crate::Bytes(self.raw.len())
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ContentType::Other {
|
ContentType::Text { .. } => {
|
||||||
name: Some(ref name),
|
if let Some(name) = self.filename() {
|
||||||
..
|
write!(
|
||||||
} => write!(f, "\"{}\", [{}]", name, self.mime_type()),
|
f,
|
||||||
ContentType::Other { .. } => write!(f, "Data attachment of type {}", self.mime_type()),
|
"\"{}\", [{}] {}",
|
||||||
ContentType::Text { ref parameters, .. }
|
name,
|
||||||
if parameters
|
self.mime_type(),
|
||||||
.iter()
|
crate::Bytes(self.raw.len())
|
||||||
.any(|(name, _)| name.eq_ignore_ascii_case(b"name")) =>
|
)
|
||||||
{
|
} else {
|
||||||
let name = String::from_utf8_lossy(
|
write!(
|
||||||
parameters
|
f,
|
||||||
.iter()
|
"Text attachment [{}] {}",
|
||||||
.find(|(name, _)| name.eq_ignore_ascii_case(b"name"))
|
self.mime_type(),
|
||||||
.map(|(_, value)| value)
|
crate::Bytes(self.raw.len())
|
||||||
.unwrap(),
|
)
|
||||||
);
|
}
|
||||||
write!(f, "\"{}\", [{}]", name, self.mime_type())
|
|
||||||
}
|
}
|
||||||
ContentType::Text { .. } => write!(f, "Text attachment of type {}", self.mime_type()),
|
|
||||||
ContentType::Multipart {
|
ContentType::Multipart {
|
||||||
parts: ref sub_att_vec,
|
parts: ref sub_att_vec,
|
||||||
..
|
..
|
||||||
} => write!(
|
} => write!(
|
||||||
f,
|
f,
|
||||||
"{} attachment with {} subs",
|
"{} attachment with {} parts",
|
||||||
self.mime_type(),
|
self.mime_type(),
|
||||||
sub_att_vec.len()
|
sub_att_vec.len()
|
||||||
),
|
),
|
||||||
|
@ -627,6 +644,16 @@ impl Attachment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_encrypted(&self) -> bool {
|
||||||
|
match self.content_type {
|
||||||
|
ContentType::Multipart {
|
||||||
|
kind: MultipartType::Encrypted,
|
||||||
|
..
|
||||||
|
} => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_signed(&self) -> bool {
|
pub fn is_signed(&self) -> bool {
|
||||||
match self.content_type {
|
match self.content_type {
|
||||||
ContentType::Multipart {
|
ContentType::Multipart {
|
||||||
|
@ -641,7 +668,7 @@ impl Attachment {
|
||||||
let mut ret = String::with_capacity(2 * self.raw.len());
|
let mut ret = String::with_capacity(2 * self.raw.len());
|
||||||
fn into_raw_helper(a: &Attachment, ret: &mut String) {
|
fn into_raw_helper(a: &Attachment, ret: &mut String) {
|
||||||
ret.push_str(&format!(
|
ret.push_str(&format!(
|
||||||
"Content-Transfer-Encoding: {}\n",
|
"Content-Transfer-Encoding: {}\r\n",
|
||||||
a.content_transfer_encoding
|
a.content_transfer_encoding
|
||||||
));
|
));
|
||||||
match &a.content_type {
|
match &a.content_type {
|
||||||
|
@ -667,7 +694,7 @@ impl Attachment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ret.push_str("\n\n");
|
ret.push_str("\r\n\r\n");
|
||||||
ret.push_str(&String::from_utf8_lossy(a.body()));
|
ret.push_str(&String::from_utf8_lossy(a.body()));
|
||||||
}
|
}
|
||||||
ContentType::Multipart {
|
ContentType::Multipart {
|
||||||
|
@ -680,36 +707,36 @@ impl Attachment {
|
||||||
if *kind == MultipartType::Signed {
|
if *kind == MultipartType::Signed {
|
||||||
ret.push_str("; micalg=pgp-sha512; protocol=\"application/pgp-signature\"");
|
ret.push_str("; micalg=pgp-sha512; protocol=\"application/pgp-signature\"");
|
||||||
}
|
}
|
||||||
ret.push('\n');
|
ret.push_str("\r\n");
|
||||||
|
|
||||||
let boundary_start = format!("\n--{}\n", boundary);
|
let boundary_start = format!("\r\n--{}\r\n", boundary);
|
||||||
for p in parts {
|
for p in parts {
|
||||||
ret.push_str(&boundary_start);
|
ret.push_str(&boundary_start);
|
||||||
into_raw_helper(p, ret);
|
into_raw_helper(p, ret);
|
||||||
}
|
}
|
||||||
ret.push_str(&format!("--{}--\n\n", boundary));
|
ret.push_str(&format!("--{}--\r\n\r\n", boundary));
|
||||||
}
|
}
|
||||||
ContentType::MessageRfc822 => {
|
ContentType::MessageRfc822 => {
|
||||||
ret.push_str(&format!("Content-Type: {}\n\n", a.content_type));
|
ret.push_str(&format!("Content-Type: {}\r\n\r\n", a.content_type));
|
||||||
ret.push_str(&String::from_utf8_lossy(a.body()));
|
ret.push_str(&String::from_utf8_lossy(a.body()));
|
||||||
}
|
}
|
||||||
ContentType::PGPSignature => {
|
ContentType::PGPSignature => {
|
||||||
ret.push_str(&format!("Content-Type: {}\n\n", a.content_type));
|
ret.push_str(&format!("Content-Type: {}\r\n\r\n", a.content_type));
|
||||||
ret.push_str(&String::from_utf8_lossy(a.body()));
|
ret.push_str(&String::from_utf8_lossy(a.body()));
|
||||||
}
|
}
|
||||||
ContentType::OctetStream { ref name } => {
|
ContentType::OctetStream { ref name } => {
|
||||||
if let Some(name) = name {
|
if let Some(name) = name {
|
||||||
ret.push_str(&format!(
|
ret.push_str(&format!(
|
||||||
"Content-Type: {}; name={}\n\n",
|
"Content-Type: {}; name={}\r\n\r\n",
|
||||||
a.content_type, name
|
a.content_type, name
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
ret.push_str(&format!("Content-Type: {}\n\n", a.content_type));
|
ret.push_str(&format!("Content-Type: {}\r\n\r\n", a.content_type));
|
||||||
}
|
}
|
||||||
ret.push_str(&BASE64_MIME.encode(a.body()).trim());
|
ret.push_str(&BASE64_MIME.encode(a.body()).trim());
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
ret.push_str(&format!("Content-Type: {}\n\n", a.content_type));
|
ret.push_str(&format!("Content-Type: {}\r\n\r\n", a.content_type));
|
||||||
ret.push_str(&String::from_utf8_lossy(a.body()));
|
ret.push_str(&String::from_utf8_lossy(a.body()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -752,9 +779,18 @@ impl Attachment {
|
||||||
h.eq_ignore_ascii_case(b"name") | h.eq_ignore_ascii_case(b"filename")
|
h.eq_ignore_ascii_case(b"name") | h.eq_ignore_ascii_case(b"filename")
|
||||||
})
|
})
|
||||||
.map(|(_, v)| String::from_utf8_lossy(v).to_string()),
|
.map(|(_, v)| String::from_utf8_lossy(v).to_string()),
|
||||||
ContentType::Other { name, .. } | ContentType::OctetStream { name, .. } => name.clone(),
|
ContentType::Other { .. } | ContentType::OctetStream { .. } => {
|
||||||
|
self.content_type.name().map(|s| s.to_string())
|
||||||
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
|
.map(|s| {
|
||||||
|
crate::email::parser::encodings::phrase(s.as_bytes(), false)
|
||||||
|
.map(|(_, v)| v)
|
||||||
|
.ok()
|
||||||
|
.and_then(|n| String::from_utf8(n).ok())
|
||||||
|
.unwrap_or_else(|| s)
|
||||||
|
})
|
||||||
.map(|n| n.replace(|c| std::path::is_separator(c) || c.is_ascii_control(), "_"))
|
.map(|n| n.replace(|c| std::path::is_separator(c) || c.is_ascii_control(), "_"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -806,6 +842,16 @@ fn decode_rec_helper<'a, 'b>(a: &'a Attachment, filter: &mut Option<Filter<'b>>)
|
||||||
vec.extend(decode_helper(a, filter));
|
vec.extend(decode_helper(a, filter));
|
||||||
vec
|
vec
|
||||||
}
|
}
|
||||||
|
MultipartType::Encrypted => {
|
||||||
|
let mut vec = Vec::new();
|
||||||
|
for a in parts {
|
||||||
|
if a.content_type == "application/octet-stream" {
|
||||||
|
vec.extend(decode_rec_helper(a, filter));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vec.extend(decode_helper(a, filter));
|
||||||
|
vec
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let mut vec = Vec::new();
|
let mut vec = Vec::new();
|
||||||
for a in parts {
|
for a in parts {
|
||||||
|
|
|
@ -134,3 +134,24 @@ pub fn verify_signature(a: &Attachment) -> Result<(Vec<u8>, &[u8])> {
|
||||||
)),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct DecryptionMetadata {
|
||||||
|
pub recipients: Vec<Recipient>,
|
||||||
|
pub file_name: Option<String>,
|
||||||
|
pub session_key: Option<String>,
|
||||||
|
pub is_mime: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Recipient {
|
||||||
|
pub keyid: Option<String>,
|
||||||
|
pub status: Result<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct SignatureMetadata {
|
||||||
|
pub signatures: Vec<Recipient>,
|
||||||
|
pub file_name: Option<String>,
|
||||||
|
pub is_mime: bool,
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,155 @@
|
||||||
|
/*
|
||||||
|
* melib - gpgme module
|
||||||
|
*
|
||||||
|
* Copyright 2020 Manos Pitsidianakis
|
||||||
|
*
|
||||||
|
* This file is part of meli.
|
||||||
|
*
|
||||||
|
* meli is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* meli is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use std::io::{self, Read, Seek, Write};
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
struct TagData {
|
||||||
|
idx: usize,
|
||||||
|
fd: ::std::os::raw::c_int,
|
||||||
|
io_state: Arc<Mutex<IoState>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe extern "C" fn gpgme_register_io_cb(
|
||||||
|
data: *mut ::std::os::raw::c_void,
|
||||||
|
fd: ::std::os::raw::c_int,
|
||||||
|
dir: ::std::os::raw::c_int,
|
||||||
|
fnc: gpgme_io_cb_t,
|
||||||
|
fnc_data: *mut ::std::os::raw::c_void,
|
||||||
|
tag: *mut *mut ::std::os::raw::c_void,
|
||||||
|
) -> gpgme_error_t {
|
||||||
|
let io_state: Arc<Mutex<IoState>> = Arc::from_raw(data as *const _);
|
||||||
|
let io_state_copy = io_state.clone();
|
||||||
|
let mut io_state_lck = io_state.lock().unwrap();
|
||||||
|
let idx = io_state_lck.max_idx;
|
||||||
|
io_state_lck.max_idx += 1;
|
||||||
|
let (sender, receiver) = smol::channel::unbounded();
|
||||||
|
let gpgfd = GpgmeFd {
|
||||||
|
fd,
|
||||||
|
fnc,
|
||||||
|
fnc_data,
|
||||||
|
idx,
|
||||||
|
write: dir == 0,
|
||||||
|
sender,
|
||||||
|
receiver,
|
||||||
|
io_state: io_state_copy.clone(),
|
||||||
|
};
|
||||||
|
let tag_data = Arc::into_raw(Arc::new(TagData {
|
||||||
|
idx,
|
||||||
|
fd,
|
||||||
|
io_state: io_state_copy,
|
||||||
|
}));
|
||||||
|
core::ptr::write(tag, tag_data as *mut _);
|
||||||
|
io_state_lck.ops.insert(idx, gpgfd);
|
||||||
|
drop(io_state_lck);
|
||||||
|
let _ = Arc::into_raw(io_state);
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe extern "C" fn gpgme_remove_io_cb(tag: *mut ::std::os::raw::c_void) {
|
||||||
|
let tag_data: Arc<TagData> = Arc::from_raw(tag as *const _);
|
||||||
|
let mut io_state_lck = tag_data.io_state.lock().unwrap();
|
||||||
|
let fd = io_state_lck.ops.remove(&tag_data.idx).unwrap();
|
||||||
|
fd.sender.try_send(()).unwrap();
|
||||||
|
drop(io_state_lck);
|
||||||
|
let _ = Arc::into_raw(tag_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub unsafe extern "C" fn gpgme_event_io_cb(
|
||||||
|
data: *mut ::std::os::raw::c_void,
|
||||||
|
type_: gpgme_event_io_t,
|
||||||
|
type_data: *mut ::std::os::raw::c_void,
|
||||||
|
) {
|
||||||
|
if type_ == gpgme_event_io_t_GPGME_EVENT_DONE {
|
||||||
|
let err = type_data as gpgme_io_event_done_data_t;
|
||||||
|
let io_state: Arc<Mutex<IoState>> = Arc::from_raw(data as *const _);
|
||||||
|
let mut io_state_lck = io_state.lock().unwrap();
|
||||||
|
io_state_lck.sender.try_send(()).unwrap();
|
||||||
|
*io_state_lck.done.lock().unwrap() = Some(gpgme_error_try(&io_state_lck.lib, (*err).err));
|
||||||
|
drop(io_state_lck);
|
||||||
|
let _ = Arc::into_raw(io_state);
|
||||||
|
} else if type_ == gpgme_event_io_t_GPGME_EVENT_NEXT_KEY {
|
||||||
|
if let Some(inner) = core::ptr::NonNull::new(type_data as gpgme_key_t) {
|
||||||
|
let io_state: Arc<Mutex<IoState>> = Arc::from_raw(data as *const _);
|
||||||
|
let io_state_lck = io_state.lock().unwrap();
|
||||||
|
io_state_lck
|
||||||
|
.key_sender
|
||||||
|
.try_send(KeyInner { inner })
|
||||||
|
.unwrap();
|
||||||
|
drop(io_state_lck);
|
||||||
|
let _ = Arc::into_raw(io_state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Read for Data {
|
||||||
|
#[inline]
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
let result = unsafe {
|
||||||
|
let (buf, len) = (buf.as_mut_ptr() as *mut _, buf.len());
|
||||||
|
call!(self.lib, gpgme_data_read)(self.inner.as_ptr(), buf, len)
|
||||||
|
};
|
||||||
|
if result >= 0 {
|
||||||
|
Ok(result as usize)
|
||||||
|
} else {
|
||||||
|
Err(io::Error::last_os_error().into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Write for Data {
|
||||||
|
#[inline]
|
||||||
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||||
|
let result = unsafe {
|
||||||
|
let (buf, len) = (buf.as_ptr() as *const _, buf.len());
|
||||||
|
call!(self.lib, gpgme_data_write)(self.inner.as_ptr(), buf, len)
|
||||||
|
};
|
||||||
|
if result >= 0 {
|
||||||
|
Ok(result as usize)
|
||||||
|
} else {
|
||||||
|
Err(io::Error::last_os_error().into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Seek for Data {
|
||||||
|
#[inline]
|
||||||
|
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
|
||||||
|
use std::convert::TryInto;
|
||||||
|
let (off, whence) = match pos {
|
||||||
|
io::SeekFrom::Start(off) => (off.try_into().unwrap_or(i64::MAX), libc::SEEK_SET),
|
||||||
|
io::SeekFrom::End(off) => (off.saturating_abs(), libc::SEEK_END),
|
||||||
|
io::SeekFrom::Current(off) => (off, libc::SEEK_CUR),
|
||||||
|
};
|
||||||
|
let result = unsafe { call!(self.lib, gpgme_data_seek)(self.inner.as_ptr(), off, whence) };
|
||||||
|
if result >= 0 {
|
||||||
|
Ok(result as u64)
|
||||||
|
} else {
|
||||||
|
Err(io::Error::last_os_error().into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,798 @@
|
||||||
|
/*
|
||||||
|
* melib - gpgme module
|
||||||
|
*
|
||||||
|
* Copyright 2020 Manos Pitsidianakis
|
||||||
|
*
|
||||||
|
* This file is part of meli.
|
||||||
|
*
|
||||||
|
* meli is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* meli is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use crate::email::pgp::{DecryptionMetadata, Recipient};
|
||||||
|
use crate::error::{ErrorKind, IntoMeliError, MeliError, Result, ResultIntoMeliError};
|
||||||
|
use futures::FutureExt;
|
||||||
|
use smol::Async;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::ffi::{CStr, CString, OsStr};
|
||||||
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
|
use std::path::Path;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use std::future::Future;
|
||||||
|
|
||||||
|
macro_rules! call {
|
||||||
|
($lib:expr, $func:ty) => {{
|
||||||
|
let func: libloading::Symbol<$func> =
|
||||||
|
$lib.get(stringify!($func).as_bytes()).expect(concat!(
|
||||||
|
"Could not use libgpgme: symbol ",
|
||||||
|
stringify!($func),
|
||||||
|
" not found!"
|
||||||
|
));
|
||||||
|
func
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
mod bindings;
|
||||||
|
use bindings::*;
|
||||||
|
mod io;
|
||||||
|
|
||||||
|
struct IoState {
|
||||||
|
max_idx: usize,
|
||||||
|
ops: HashMap<usize, GpgmeFd>,
|
||||||
|
done: Arc<Mutex<Option<Result<()>>>>,
|
||||||
|
sender: smol::channel::Sender<()>,
|
||||||
|
receiver: smol::channel::Receiver<()>,
|
||||||
|
key_sender: smol::channel::Sender<KeyInner>,
|
||||||
|
key_receiver: smol::channel::Receiver<KeyInner>,
|
||||||
|
lib: Arc<libloading::Library>,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for IoState {}
|
||||||
|
unsafe impl Sync for IoState {}
|
||||||
|
|
||||||
|
pub struct ContextInner {
|
||||||
|
inner: core::ptr::NonNull<gpgme_context>,
|
||||||
|
lib: Arc<libloading::Library>,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for ContextInner {}
|
||||||
|
unsafe impl Sync for ContextInner {}
|
||||||
|
|
||||||
|
pub struct Context {
|
||||||
|
inner: Arc<ContextInner>,
|
||||||
|
io_state: Arc<Mutex<IoState>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for Context {}
|
||||||
|
unsafe impl Sync for Context {}
|
||||||
|
|
||||||
|
impl Drop for ContextInner {
|
||||||
|
#[inline]
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { call!(self.lib, gpgme_release)(self.inner.as_mut()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Context {
|
||||||
|
pub fn new() -> Result<Self> {
|
||||||
|
let version = CString::new("1.12.0").unwrap();
|
||||||
|
let lib = Arc::new(libloading::Library::new("libgpgme.so")?);
|
||||||
|
unsafe { call!(&lib, gpgme_check_version)(version.as_c_str().as_ptr() as *mut _) };
|
||||||
|
let (sender, receiver) = smol::channel::unbounded();
|
||||||
|
let (key_sender, key_receiver) = smol::channel::unbounded();
|
||||||
|
|
||||||
|
let mut ptr = core::ptr::null_mut();
|
||||||
|
let io_state = Arc::new(Mutex::new(IoState {
|
||||||
|
max_idx: 0,
|
||||||
|
ops: HashMap::default(),
|
||||||
|
done: Arc::new(Mutex::new(None)),
|
||||||
|
sender,
|
||||||
|
receiver,
|
||||||
|
key_sender,
|
||||||
|
key_receiver,
|
||||||
|
lib: lib.clone(),
|
||||||
|
}));
|
||||||
|
let add_priv_data = io_state.clone();
|
||||||
|
let event_priv_data = io_state.clone();
|
||||||
|
|
||||||
|
let mut io_cbs = gpgme_io_cbs {
|
||||||
|
add: Some(io::gpgme_register_io_cb),
|
||||||
|
add_priv: Arc::into_raw(add_priv_data) as *mut ::std::os::raw::c_void, //add_priv: *mut ::std::os::raw::c_void,
|
||||||
|
remove: Some(io::gpgme_remove_io_cb),
|
||||||
|
event: Some(io::gpgme_event_io_cb),
|
||||||
|
event_priv: Arc::into_raw(event_priv_data) as *mut ::std::os::raw::c_void, //pub event_priv: *mut ::std::os::raw::c_void,
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
gpgme_error_try(&lib, call!(&lib, gpgme_new)(&mut ptr))?;
|
||||||
|
call!(&lib, gpgme_set_io_cbs)(ptr, &mut io_cbs);
|
||||||
|
}
|
||||||
|
Ok(Context {
|
||||||
|
inner: Arc::new(ContextInner {
|
||||||
|
inner: core::ptr::NonNull::new(ptr).ok_or_else(|| {
|
||||||
|
MeliError::new("Could not use libgpgme").set_kind(ErrorKind::Bug)
|
||||||
|
})?,
|
||||||
|
lib,
|
||||||
|
}),
|
||||||
|
io_state,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_data_mem(&self, bytes: &[u8]) -> Result<Data> {
|
||||||
|
let mut ptr = core::ptr::null_mut();
|
||||||
|
unsafe {
|
||||||
|
gpgme_error_try(
|
||||||
|
&self.inner.lib,
|
||||||
|
call!(&self.inner.lib, gpgme_data_new_from_mem)(
|
||||||
|
&mut ptr,
|
||||||
|
bytes.as_ptr() as *const ::std::os::raw::c_char,
|
||||||
|
bytes.len(),
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Data {
|
||||||
|
lib: self.inner.lib.clone(),
|
||||||
|
kind: DataKind::Memory,
|
||||||
|
inner: core::ptr::NonNull::new(ptr).ok_or_else(|| {
|
||||||
|
MeliError::new("Could not create libgpgme data").set_kind(ErrorKind::Bug)
|
||||||
|
})?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_data_file<P: AsRef<Path>>(&self, r: P) -> Result<Data> {
|
||||||
|
let path: &Path = r.as_ref();
|
||||||
|
if !path.exists() {
|
||||||
|
return Err(MeliError::new(format!(
|
||||||
|
"File `{}` doesn't exist.",
|
||||||
|
path.display()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
let os_str: &OsStr = path.as_ref();
|
||||||
|
let b = CString::new(os_str.as_bytes())?;
|
||||||
|
let mut ptr = core::ptr::null_mut();
|
||||||
|
unsafe {
|
||||||
|
let ret: GpgmeError = call!(&self.inner.lib, gpgme_data_new_from_file)(
|
||||||
|
&mut ptr,
|
||||||
|
b.as_ptr() as *const ::std::os::raw::c_char,
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
gpgme_error_try(&self.inner.lib, ret)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Data {
|
||||||
|
lib: self.inner.lib.clone(),
|
||||||
|
kind: DataKind::Memory,
|
||||||
|
inner: core::ptr::NonNull::new(ptr).ok_or_else(|| {
|
||||||
|
MeliError::new("Could not create libgpgme data").set_kind(ErrorKind::Bug)
|
||||||
|
})?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify(
|
||||||
|
&mut self,
|
||||||
|
mut signature: Data,
|
||||||
|
mut text: Data,
|
||||||
|
) -> Result<impl Future<Output = Result<()>> + Send> {
|
||||||
|
unsafe {
|
||||||
|
gpgme_error_try(
|
||||||
|
&self.inner.lib,
|
||||||
|
call!(&self.inner.lib, gpgme_op_verify_start)(
|
||||||
|
self.inner.inner.as_ptr(),
|
||||||
|
signature.inner.as_mut(),
|
||||||
|
text.inner.as_mut(),
|
||||||
|
std::ptr::null_mut(),
|
||||||
|
),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
let _s = signature;
|
||||||
|
let _t = text;
|
||||||
|
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;
|
||||||
|
debug!("done with fut join");
|
||||||
|
let rcv = {
|
||||||
|
let io_state_lck = io_state.lock().unwrap();
|
||||||
|
io_state_lck.receiver.clone()
|
||||||
|
};
|
||||||
|
let _ = rcv.recv().await;
|
||||||
|
{
|
||||||
|
let verify_result =
|
||||||
|
unsafe { call!(&ctx.lib, gpgme_op_verify_result)(ctx.inner.as_ptr()) };
|
||||||
|
if verify_result.is_null() {
|
||||||
|
return Err(MeliError::new(
|
||||||
|
"Unspecified libgpgme error: gpgme_op_verify_result returned NULL.",
|
||||||
|
)
|
||||||
|
.set_err_kind(ErrorKind::External));
|
||||||
|
}
|
||||||
|
drop(verify_result);
|
||||||
|
}
|
||||||
|
let io_state_lck = io_state.lock().unwrap();
|
||||||
|
let ret = io_state_lck
|
||||||
|
.done
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.take()
|
||||||
|
.unwrap_or_else(|| Err(MeliError::new("Unspecified libgpgme error")));
|
||||||
|
ret
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn keylist(&mut self) -> Result<impl Future<Output = Result<Vec<String>>>> {
|
||||||
|
unsafe {
|
||||||
|
gpgme_error_try(
|
||||||
|
&self.inner.lib,
|
||||||
|
call!(&self.inner.lib, gpgme_op_keylist_start)(
|
||||||
|
self.inner.inner.as_ptr(),
|
||||||
|
std::ptr::null_mut(),
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
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, key_receiver) = {
|
||||||
|
let io_state_lck = io_state.lock().unwrap();
|
||||||
|
(
|
||||||
|
io_state_lck.receiver.clone(),
|
||||||
|
io_state_lck.key_receiver.clone(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let _ = rcv.recv().await;
|
||||||
|
unsafe {
|
||||||
|
gpgme_error_try(
|
||||||
|
&ctx.lib,
|
||||||
|
call!(&ctx.lib, gpgme_op_keylist_end)(ctx.inner.as_ptr()),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
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 mut keys = vec![];
|
||||||
|
while let Ok(key) = key_receiver.try_recv() {
|
||||||
|
unsafe {
|
||||||
|
if (*(key.inner.as_ptr())).uids.is_null() {
|
||||||
|
keys.push("null".to_string());
|
||||||
|
} else {
|
||||||
|
keys.push(format!(
|
||||||
|
"{} <{}>",
|
||||||
|
CStr::from_ptr((*((*(key.inner.as_ptr())).uids)).name)
|
||||||
|
.to_string_lossy(),
|
||||||
|
CStr::from_ptr((*((*(key.inner.as_ptr())).uids)).email)
|
||||||
|
.to_string_lossy()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(keys)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sign<'d>(
|
||||||
|
&mut self,
|
||||||
|
text: &'d mut Data,
|
||||||
|
) -> Result<impl Future<Output = Result<()>> + 'd> {
|
||||||
|
let sig = std::ptr::null_mut();
|
||||||
|
unsafe {
|
||||||
|
gpgme_error_try(
|
||||||
|
&self.inner.lib,
|
||||||
|
call!(&self.inner.lib, gpgme_op_sign_start)(
|
||||||
|
self.inner.inner.as_ptr(),
|
||||||
|
text.inner.as_mut(),
|
||||||
|
sig,
|
||||||
|
gpgme_sig_mode_t_GPGME_SIG_MODE_DETACH,
|
||||||
|
),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
let ret = io_state_lck
|
||||||
|
.done
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.take()
|
||||||
|
.unwrap_or_else(|| Err(MeliError::new("Unspecified libgpgme error")));
|
||||||
|
ret
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decrypt(
|
||||||
|
&mut self,
|
||||||
|
mut cipher: Data,
|
||||||
|
) -> Result<impl Future<Output = Result<(DecryptionMetadata, Vec<u8>)>> + Send> {
|
||||||
|
let mut plain: gpgme_data_t = std::ptr::null_mut();
|
||||||
|
unsafe {
|
||||||
|
gpgme_error_try(
|
||||||
|
&self.inner.lib,
|
||||||
|
call!(&self.inner.lib, gpgme_data_new)(&mut plain),
|
||||||
|
)?;
|
||||||
|
gpgme_error_try(
|
||||||
|
&self.inner.lib,
|
||||||
|
call!(&self.inner.lib, gpgme_op_decrypt_start)(
|
||||||
|
self.inner.inner.as_ptr(),
|
||||||
|
cipher.inner.as_mut(),
|
||||||
|
plain,
|
||||||
|
),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
let mut plain = Data {
|
||||||
|
lib: self.inner.lib.clone(),
|
||||||
|
kind: DataKind::Memory,
|
||||||
|
inner: core::ptr::NonNull::new(plain).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 {
|
||||||
|
let _c = cipher;
|
||||||
|
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 decrypt_result =
|
||||||
|
unsafe { call!(&ctx.lib, gpgme_op_decrypt_result)(ctx.inner.as_ptr()) };
|
||||||
|
if decrypt_result.is_null() {
|
||||||
|
return Err(MeliError::new(
|
||||||
|
"Unspecified libgpgme error: gpgme_op_decrypt_result returned NULL.",
|
||||||
|
)
|
||||||
|
.set_err_kind(ErrorKind::External));
|
||||||
|
}
|
||||||
|
let mut recipients = vec![];
|
||||||
|
let is_mime;
|
||||||
|
let file_name;
|
||||||
|
let session_key;
|
||||||
|
unsafe {
|
||||||
|
is_mime = (*decrypt_result).is_mime() > 0;
|
||||||
|
file_name = if !(*decrypt_result).file_name.is_null() {
|
||||||
|
Some(
|
||||||
|
CStr::from_ptr((*decrypt_result).file_name)
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
session_key = if !(*decrypt_result).session_key.is_null() {
|
||||||
|
Some(
|
||||||
|
CStr::from_ptr((*decrypt_result).session_key)
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let mut recipient_iter = (*decrypt_result).recipients;
|
||||||
|
while !recipient_iter.is_null() {
|
||||||
|
recipients.push(Recipient {
|
||||||
|
keyid: if !(*recipient_iter).keyid.is_null() {
|
||||||
|
Some(
|
||||||
|
CStr::from_ptr((*recipient_iter).keyid)
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
status: gpgme_error_try(&ctx.lib, (*recipient_iter).status),
|
||||||
|
});
|
||||||
|
recipient_iter = (*recipient_iter).next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
use std::io::Seek;
|
||||||
|
/* Rewind cursor */
|
||||||
|
plain
|
||||||
|
.seek(std::io::SeekFrom::Start(0))
|
||||||
|
.chain_err_summary(|| "libgpgme error: could not perform seek on plain text")?;
|
||||||
|
Ok((
|
||||||
|
DecryptionMetadata {
|
||||||
|
recipients,
|
||||||
|
file_name,
|
||||||
|
session_key,
|
||||||
|
is_mime,
|
||||||
|
},
|
||||||
|
plain.into_bytes()?,
|
||||||
|
))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gpgme_error_try(lib: &libloading::Library, error_code: GpgmeError) -> Result<()> {
|
||||||
|
const ERR_MAX_LEN: usize = 256;
|
||||||
|
if error_code == 0 {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let mut buf: Vec<u8> = vec![0; ERR_MAX_LEN];
|
||||||
|
unsafe {
|
||||||
|
call!(lib, gpgme_strerror_r)(
|
||||||
|
error_code,
|
||||||
|
buf.as_mut_ptr() as *mut ::std::os::raw::c_char,
|
||||||
|
ERR_MAX_LEN,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
while buf.ends_with(&b"\0"[..]) {
|
||||||
|
buf.pop();
|
||||||
|
}
|
||||||
|
Err(MeliError::from(
|
||||||
|
String::from_utf8(buf)
|
||||||
|
.unwrap_or_else(|err| String::from_utf8_lossy(&err.into_bytes()).to_string()),
|
||||||
|
)
|
||||||
|
.set_summary(format!("libgpgme error {}", error_code)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum DataKind {
|
||||||
|
Memory,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Data {
|
||||||
|
inner: core::ptr::NonNull<bindings::gpgme_data>,
|
||||||
|
kind: DataKind,
|
||||||
|
lib: Arc<libloading::Library>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Data {
|
||||||
|
pub fn into_bytes(mut self) -> Result<Vec<u8>> {
|
||||||
|
use std::io::Read;
|
||||||
|
let mut buf = vec![];
|
||||||
|
self.read_to_end(&mut buf)?;
|
||||||
|
Ok(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for Data {}
|
||||||
|
unsafe impl Sync for Data {}
|
||||||
|
|
||||||
|
impl Drop for Data {
|
||||||
|
#[inline]
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if !self.inner.as_ptr().is_null() {
|
||||||
|
match self.kind {
|
||||||
|
DataKind::Memory => unsafe {
|
||||||
|
call!(self.lib, gpgme_data_release)(self.inner.as_mut())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct GpgmeFd {
|
||||||
|
fd: RawFd,
|
||||||
|
fnc: GpgmeIOCb,
|
||||||
|
fnc_data: *mut ::std::os::raw::c_void,
|
||||||
|
idx: usize,
|
||||||
|
write: bool,
|
||||||
|
sender: smol::channel::Sender<()>,
|
||||||
|
receiver: smol::channel::Receiver<()>,
|
||||||
|
io_state: Arc<Mutex<IoState>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for GpgmeFd {}
|
||||||
|
unsafe impl Sync for GpgmeFd {}
|
||||||
|
|
||||||
|
impl AsRawFd for GpgmeFd {
|
||||||
|
fn as_raw_fd(&self) -> RawFd {
|
||||||
|
self.fd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct KeyInner {
|
||||||
|
inner: core::ptr::NonNull<_gpgme_key>,
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl Send for KeyInner {}
|
||||||
|
unsafe impl Sync for KeyInner {}
|
||||||
|
|
||||||
|
pub struct Key {
|
||||||
|
inner: KeyInner,
|
||||||
|
lib: Arc<libloading::Library>,
|
||||||
|
}
|
||||||
|
unsafe impl Send for Key {}
|
||||||
|
unsafe impl Sync for Key {}
|
||||||
|
|
||||||
|
impl Clone for Key {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
let lib = self.lib.clone();
|
||||||
|
unsafe {
|
||||||
|
call!(&self.lib, gpgme_key_ref)(self.inner.inner.as_ptr());
|
||||||
|
}
|
||||||
|
Key {
|
||||||
|
inner: self.inner.clone(),
|
||||||
|
lib,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Key {
|
||||||
|
#[inline]
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
call!(&self.lib, gpgme_key_unref)(self.inner.inner.as_ptr());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//#[test]
|
||||||
|
//fn test_gpgme() {
|
||||||
|
// std::thread::spawn(move || {
|
||||||
|
// let ex = smol::Executor::new();
|
||||||
|
// futures::executor::block_on(ex.run(futures::future::pending::<()>()));
|
||||||
|
// });
|
||||||
|
// let mut ctx = Context::new().unwrap();
|
||||||
|
// //let sig = ctx.new_data_mem("sign").unwrap();
|
||||||
|
// //let text = ctx.new_data_mem("file").unwrap();
|
||||||
|
// let sig = ctx.new_data_mem(include_bytes!("/tmp/sig")).unwrap();
|
||||||
|
// let text = ctx.new_data_mem(include_bytes!("/tmp/data")).unwrap();
|
||||||
|
//
|
||||||
|
// futures::executor::block_on(ctx.verify(sig, text).unwrap()).unwrap();
|
||||||
|
// println!(
|
||||||
|
// "keys = {:#?}",
|
||||||
|
// futures::executor::block_on(ctx.keylist().unwrap()).unwrap()
|
||||||
|
// );
|
||||||
|
// let cipher = ctx.new_data_file("/tmp/msg.asc").unwrap();
|
||||||
|
// let plain = futures::executor::block_on(ctx.decrypt(cipher).unwrap()).unwrap();
|
||||||
|
// println!(
|
||||||
|
// "buf: {}",
|
||||||
|
// String::from_utf8_lossy(&plain.into_bytes().unwrap())
|
||||||
|
// );
|
||||||
|
//}
|
|
@ -119,6 +119,8 @@ pub mod connections;
|
||||||
pub mod parsec;
|
pub mod parsec;
|
||||||
pub mod search;
|
pub mod search;
|
||||||
|
|
||||||
|
#[cfg(feature = "gpgme")]
|
||||||
|
pub mod gpgme;
|
||||||
#[cfg(feature = "smtp")]
|
#[cfg(feature = "smtp")]
|
||||||
pub mod smtp;
|
pub mod smtp;
|
||||||
#[cfg(feature = "sqlite3")]
|
#[cfg(feature = "sqlite3")]
|
||||||
|
@ -140,6 +142,35 @@ pub extern crate smol;
|
||||||
pub extern crate uuid;
|
pub extern crate uuid;
|
||||||
pub extern crate xdg_utils;
|
pub extern crate xdg_utils;
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub struct Bytes(pub usize);
|
||||||
|
|
||||||
|
impl Bytes {
|
||||||
|
pub const KILOBYTE: f64 = 1024.0;
|
||||||
|
pub const MEGABYTE: f64 = Self::KILOBYTE * 1024.0;
|
||||||
|
pub const GIGABYTE: f64 = Self::MEGABYTE * 1024.0;
|
||||||
|
pub const PETABYTE: f64 = Self::GIGABYTE * 1024.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl core::fmt::Display for Bytes {
|
||||||
|
fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||||
|
let bytes: f64 = self.0 as f64;
|
||||||
|
if bytes == 0.0 {
|
||||||
|
write!(fmt, "0")
|
||||||
|
} else if bytes < Self::KILOBYTE {
|
||||||
|
write!(fmt, "{:.2} bytes", bytes)
|
||||||
|
} else if bytes < Self::MEGABYTE {
|
||||||
|
write!(fmt, "{:.2} KiB", bytes / Self::KILOBYTE)
|
||||||
|
} else if bytes < Self::GIGABYTE {
|
||||||
|
write!(fmt, "{:.2} MiB", bytes / Self::MEGABYTE)
|
||||||
|
} else if bytes < Self::PETABYTE {
|
||||||
|
write!(fmt, "{:.2} GiB", bytes / Self::GIGABYTE)
|
||||||
|
} else {
|
||||||
|
write!(fmt, "{:.2} PiB", bytes / Self::PETABYTE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub use shellexpand::ShellExpandTrait;
|
pub use shellexpand::ShellExpandTrait;
|
||||||
pub mod shellexpand {
|
pub mod shellexpand {
|
||||||
|
|
||||||
|
|
|
@ -428,7 +428,7 @@ impl Composer {
|
||||||
write_string_to_grid(
|
write_string_to_grid(
|
||||||
&format!(
|
&format!(
|
||||||
"☑ sign with {}",
|
"☑ sign with {}",
|
||||||
account_settings!(context[self.account_hash].pgp.key)
|
account_settings!(context[self.account_hash].pgp.sign_key)
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|s| s.as_str())
|
.map(|s| s.as_str())
|
||||||
.unwrap_or("default key")
|
.unwrap_or("default key")
|
||||||
|
@ -1473,7 +1473,7 @@ pub fn send_draft(
|
||||||
account_settings!(context[account_hash].pgp.gpg_binary)
|
account_settings!(context[account_hash].pgp.gpg_binary)
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|s| s.as_str()),
|
.map(|s| s.as_str()),
|
||||||
account_settings!(context[account_hash].pgp.key)
|
account_settings!(context[account_hash].pgp.sign_key)
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|s| s.as_str()),
|
.map(|s| s.as_str()),
|
||||||
);
|
);
|
||||||
|
|
|
@ -20,15 +20,38 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use melib::email::pgp as melib_pgp;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
|
|
||||||
pub fn verify_signature(a: &Attachment, context: &mut Context) -> Vec<u8> {
|
pub fn verify_signature(a: &Attachment, context: &mut Context) -> Result<Vec<u8>> {
|
||||||
match melib::signatures::verify_signature(a) {
|
let (bytes, sig) =
|
||||||
Ok((bytes, sig)) => {
|
melib_pgp::verify_signature(a).chain_err_summary(|| "Could not verify signature.")?;
|
||||||
let bytes_file = create_temp_file(&bytes, None, None, true);
|
let bytes_file = create_temp_file(&bytes, None, None, true);
|
||||||
let signature_file = create_temp_file(sig, None, None, true);
|
let signature_file = create_temp_file(sig, None, None, true);
|
||||||
match Command::new(
|
let binary = context
|
||||||
|
.settings
|
||||||
|
.pgp
|
||||||
|
.gpg_binary
|
||||||
|
.as_ref()
|
||||||
|
.map(String::as_str)
|
||||||
|
.unwrap_or("gpg2");
|
||||||
|
Ok(Command::new(binary)
|
||||||
|
.args(&[
|
||||||
|
"--output",
|
||||||
|
"-",
|
||||||
|
"--verify",
|
||||||
|
signature_file.path.to_str().unwrap(),
|
||||||
|
bytes_file.path.to_str().unwrap(),
|
||||||
|
])
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
.and_then(|gpg| gpg.wait_with_output())
|
||||||
|
.map(|gpg| gpg.stderr)
|
||||||
|
.chain_err_summary(|| {
|
||||||
|
format!(
|
||||||
|
"Failed to launch {} to verify PGP signature",
|
||||||
context
|
context
|
||||||
.settings
|
.settings
|
||||||
.pgp
|
.pgp
|
||||||
|
@ -37,50 +60,7 @@ pub fn verify_signature(a: &Attachment, context: &mut Context) -> Vec<u8> {
|
||||||
.map(String::as_str)
|
.map(String::as_str)
|
||||||
.unwrap_or("gpg2"),
|
.unwrap_or("gpg2"),
|
||||||
)
|
)
|
||||||
.args(&[
|
})?)
|
||||||
"--output",
|
|
||||||
"-",
|
|
||||||
"--verify",
|
|
||||||
signature_file.path.to_str().unwrap(),
|
|
||||||
bytes_file.path.to_str().unwrap(),
|
|
||||||
])
|
|
||||||
.stdin(Stdio::piped())
|
|
||||||
.stderr(Stdio::piped())
|
|
||||||
.spawn()
|
|
||||||
{
|
|
||||||
Ok(gpg) => {
|
|
||||||
return gpg.wait_with_output().unwrap().stderr;
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
context.replies.push_back(UIEvent::Notification(
|
|
||||||
Some(format!(
|
|
||||||
"Failed to launch {} to verify PGP signature",
|
|
||||||
context
|
|
||||||
.settings
|
|
||||||
.pgp
|
|
||||||
.gpg_binary
|
|
||||||
.as_ref()
|
|
||||||
.map(String::as_str)
|
|
||||||
.unwrap_or("gpg2"),
|
|
||||||
)),
|
|
||||||
format!(
|
|
||||||
"{}\nsee meli.conf(5) for configuration setting pgp.gpg_binary",
|
|
||||||
&err
|
|
||||||
),
|
|
||||||
Some(NotificationType::Error(melib::error::ErrorKind::External)),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
context.replies.push_back(UIEvent::Notification(
|
|
||||||
Some("Could not verify signature.".to_string()),
|
|
||||||
err.to_string(),
|
|
||||||
Some(NotificationType::Error(err.kind)),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Vec::new()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns multipart/signed
|
/// Returns multipart/signed
|
||||||
|
@ -89,7 +69,8 @@ pub fn sign(
|
||||||
gpg_binary: Option<&str>,
|
gpg_binary: Option<&str>,
|
||||||
pgp_key: Option<&str>,
|
pgp_key: Option<&str>,
|
||||||
) -> Result<AttachmentBuilder> {
|
) -> Result<AttachmentBuilder> {
|
||||||
let mut command = Command::new(gpg_binary.unwrap_or("gpg2"));
|
let binary = gpg_binary.unwrap_or("gpg2");
|
||||||
|
let mut command = Command::new(binary);
|
||||||
command.args(&[
|
command.args(&[
|
||||||
"--digest-algo",
|
"--digest-algo",
|
||||||
"sha512",
|
"sha512",
|
||||||
|
@ -102,23 +83,27 @@ pub fn sign(
|
||||||
command.args(&["--local-user", key]);
|
command.args(&["--local-user", key]);
|
||||||
}
|
}
|
||||||
let a: Attachment = a.into();
|
let a: Attachment = a.into();
|
||||||
let mut gpg = command
|
|
||||||
|
let sig_attachment = command
|
||||||
.stdin(Stdio::piped())
|
.stdin(Stdio::piped())
|
||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
.stderr(Stdio::null())
|
.stderr(Stdio::null())
|
||||||
.spawn()?;
|
.spawn()
|
||||||
|
.and_then(|mut gpg| {
|
||||||
let sig_attachment = {
|
gpg.stdin
|
||||||
gpg.stdin
|
.as_mut()
|
||||||
.as_mut()
|
.expect("Could not get gpg stdin")
|
||||||
.unwrap()
|
.write_all(&melib_pgp::convert_attachment_to_rfc_spec(
|
||||||
.write_all(&melib::signatures::convert_attachment_to_rfc_spec(
|
a.into_raw().as_bytes(),
|
||||||
a.into_raw().as_bytes(),
|
))?;
|
||||||
|
let gpg = gpg.wait_with_output()?;
|
||||||
|
Ok(Attachment::new(
|
||||||
|
ContentType::PGPSignature,
|
||||||
|
Default::default(),
|
||||||
|
gpg.stdout,
|
||||||
))
|
))
|
||||||
.unwrap();
|
})
|
||||||
let gpg = gpg.wait_with_output().unwrap();
|
.chain_err_summary(|| format!("Failed to launch {} to verify PGP signature", binary))?;
|
||||||
Attachment::new(ContentType::PGPSignature, Default::default(), gpg.stdout)
|
|
||||||
};
|
|
||||||
|
|
||||||
let a: AttachmentBuilder = a.into();
|
let a: AttachmentBuilder = a.into();
|
||||||
let parts = vec![a, sig_attachment.into()];
|
let parts = vec![a, sig_attachment.into()];
|
||||||
|
@ -134,3 +119,61 @@ pub fn sign(
|
||||||
)
|
)
|
||||||
.into())
|
.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn decrypt(
|
||||||
|
raw: Vec<u8>,
|
||||||
|
gpg_binary: Option<String>,
|
||||||
|
decrypt_key: Option<String>,
|
||||||
|
) -> Result<(melib_pgp::DecryptionMetadata, Vec<u8>)> {
|
||||||
|
let bin = gpg_binary.as_ref().map(|s| s.as_str()).unwrap_or("gpg2");
|
||||||
|
let mut command = Command::new(bin);
|
||||||
|
command.args(&["--digest-algo", "sha512", "--output", "-"]);
|
||||||
|
if let Some(ref key) = decrypt_key {
|
||||||
|
command.args(&["--local-user", key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let stdout = command
|
||||||
|
.args(&["--decrypt"])
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
.and_then(|mut gpg| {
|
||||||
|
gpg.stdin
|
||||||
|
.as_mut()
|
||||||
|
.expect("Could not get gpg stdin")
|
||||||
|
.write_all(&raw)?;
|
||||||
|
let gpg = gpg.wait_with_output()?;
|
||||||
|
Ok(gpg.stdout)
|
||||||
|
})
|
||||||
|
.chain_err_summary(|| format!("Failed to launch {} to verify PGP signature", bin))?;
|
||||||
|
Ok((melib_pgp::DecryptionMetadata::default(), stdout))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn verify(a: Attachment, gpg_binary: Option<String>) -> Result<Vec<u8>> {
|
||||||
|
let (bytes, sig) =
|
||||||
|
melib_pgp::verify_signature(&a).chain_err_summary(|| "Could not verify signature.")?;
|
||||||
|
let bytes_file = create_temp_file(&bytes, None, None, true);
|
||||||
|
let signature_file = create_temp_file(sig, None, None, true);
|
||||||
|
Ok(
|
||||||
|
Command::new(gpg_binary.as_ref().map(String::as_str).unwrap_or("gpg2"))
|
||||||
|
.args(&[
|
||||||
|
"--output",
|
||||||
|
"-",
|
||||||
|
"--verify",
|
||||||
|
signature_file.path.to_str().unwrap(),
|
||||||
|
bytes_file.path.to_str().unwrap(),
|
||||||
|
])
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.stderr(Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
.and_then(|gpg| gpg.wait_with_output())
|
||||||
|
.map(|gpg| gpg.stderr)
|
||||||
|
.chain_err_summary(|| {
|
||||||
|
format!(
|
||||||
|
"Failed to launch {} to verify PGP signature",
|
||||||
|
gpg_binary.as_ref().map(String::as_str).unwrap_or("gpg2"),
|
||||||
|
)
|
||||||
|
})?,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -292,12 +292,23 @@ pub struct PGPSettingsOverride {
|
||||||
#[serde(alias = "auto-verify-signatures")]
|
#[serde(alias = "auto-verify-signatures")]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub auto_verify_signatures: Option<bool>,
|
pub auto_verify_signatures: Option<bool>,
|
||||||
|
#[doc = " auto decrypt encrypted e-mail"]
|
||||||
|
#[serde(alias = "auto-decrypt")]
|
||||||
|
#[serde(default)]
|
||||||
|
pub auto_decrypt: Option<bool>,
|
||||||
#[doc = " always sign sent messages"]
|
#[doc = " always sign sent messages"]
|
||||||
#[serde(alias = "auto-sign")]
|
#[serde(alias = "auto-sign")]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub auto_sign: Option<bool>,
|
pub auto_sign: Option<bool>,
|
||||||
|
#[serde(alias = "sign-key")]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub key: Option<Option<String>>,
|
pub sign_key: Option<Option<String>>,
|
||||||
|
#[serde(alias = "decrypt-key")]
|
||||||
|
#[serde(default)]
|
||||||
|
pub decrypt_key: Option<Option<String>>,
|
||||||
|
#[serde(alias = "encrypt-key")]
|
||||||
|
#[serde(default)]
|
||||||
|
pub encrypt_key: Option<Option<String>>,
|
||||||
#[doc = " gpg binary name or file location to use"]
|
#[doc = " gpg binary name or file location to use"]
|
||||||
#[serde(alias = "gpg-binary")]
|
#[serde(alias = "gpg-binary")]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
@ -307,8 +318,11 @@ impl Default for PGPSettingsOverride {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
PGPSettingsOverride {
|
PGPSettingsOverride {
|
||||||
auto_verify_signatures: None,
|
auto_verify_signatures: None,
|
||||||
|
auto_decrypt: None,
|
||||||
auto_sign: None,
|
auto_sign: None,
|
||||||
key: None,
|
sign_key: None,
|
||||||
|
decrypt_key: None,
|
||||||
|
encrypt_key: None,
|
||||||
gpg_binary: None,
|
gpg_binary: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,13 +29,23 @@ pub struct PGPSettings {
|
||||||
#[serde(default = "true_val", alias = "auto-verify-signatures")]
|
#[serde(default = "true_val", alias = "auto-verify-signatures")]
|
||||||
pub auto_verify_signatures: bool,
|
pub auto_verify_signatures: bool,
|
||||||
|
|
||||||
|
/// auto decrypt encrypted e-mail
|
||||||
|
#[serde(default = "true_val", alias = "auto-decrypt")]
|
||||||
|
pub auto_decrypt: bool,
|
||||||
|
|
||||||
/// always sign sent messages
|
/// always sign sent messages
|
||||||
#[serde(default = "false_val", alias = "auto-sign")]
|
#[serde(default = "false_val", alias = "auto-sign")]
|
||||||
pub auto_sign: bool,
|
pub auto_sign: bool,
|
||||||
|
|
||||||
// https://tools.ietf.org/html/rfc4880#section-12.2
|
// https://tools.ietf.org/html/rfc4880#section-12.2
|
||||||
#[serde(default = "none")]
|
#[serde(default = "none", alias = "sign-key")]
|
||||||
pub key: Option<String>,
|
pub sign_key: Option<String>,
|
||||||
|
|
||||||
|
#[serde(default = "none", alias = "decrypt-key")]
|
||||||
|
pub decrypt_key: Option<String>,
|
||||||
|
|
||||||
|
#[serde(default = "none", alias = "encrypt-key")]
|
||||||
|
pub encrypt_key: Option<String>,
|
||||||
|
|
||||||
/// gpg binary name or file location to use
|
/// gpg binary name or file location to use
|
||||||
#[serde(default, alias = "gpg-binary")]
|
#[serde(default, alias = "gpg-binary")]
|
||||||
|
@ -46,8 +56,11 @@ impl Default for PGPSettings {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
PGPSettings {
|
PGPSettings {
|
||||||
auto_verify_signatures: true,
|
auto_verify_signatures: true,
|
||||||
|
auto_decrypt: true,
|
||||||
auto_sign: false,
|
auto_sign: false,
|
||||||
key: None,
|
sign_key: None,
|
||||||
|
decrypt_key: None,
|
||||||
|
encrypt_key: None,
|
||||||
gpg_binary: None,
|
gpg_binary: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,7 +113,7 @@ pub struct Context {
|
||||||
sender: Sender<ThreadEvent>,
|
sender: Sender<ThreadEvent>,
|
||||||
receiver: Receiver<ThreadEvent>,
|
receiver: Receiver<ThreadEvent>,
|
||||||
input_thread: InputHandler,
|
input_thread: InputHandler,
|
||||||
job_executor: Arc<JobExecutor>,
|
pub job_executor: Arc<JobExecutor>,
|
||||||
pub children: Vec<std::process::Child>,
|
pub children: Vec<std::process::Child>,
|
||||||
pub plugin_manager: PluginManager,
|
pub plugin_manager: PluginManager,
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue