Browse Source

add libgpgme feature

jmap-eventsource
Manos Pitsidianakis 1 year ago
parent
commit
23ca41e3e8
Signed by: epilys GPG Key ID: 73627C2F690DF710
  1. 3
      Cargo.toml
  2. 3
      melib/Cargo.toml
  3. 3
      melib/src/email.rs
  4. 90
      melib/src/email/attachment_types.rs
  5. 140
      melib/src/email/attachments.rs
  6. 21
      melib/src/email/pgp.rs
  7. 10965
      melib/src/gpgme/bindings.rs
  8. 155
      melib/src/gpgme/io.rs
  9. 798
      melib/src/gpgme/mod.rs
  10. 31
      melib/src/lib.rs
  11. 4
      src/components/mail/compose.rs
  12. 171
      src/components/mail/pgp.rs
  13. 1391
      src/components/mail/view.rs
  14. 18
      src/conf/overrides.rs
  15. 19
      src/conf/pgp.rs
  16. 2
      src/state.rs

3
Cargo.toml

@ -72,7 +72,7 @@ debug = false
members = ["melib", "tools", ]
[features]
default = ["sqlite3", "notmuch", "regexp", "smtp", "dbus-notifications"]
default = ["sqlite3", "notmuch", "regexp", "smtp", "dbus-notifications", "gpgme"]
notmuch = ["melib/notmuch_backend", ]
jmap = ["melib/jmap_backend",]
sqlite3 = ["melib/sqlite3"]
@ -81,6 +81,7 @@ regexp = ["pcre2"]
dbus-notifications = ["notify-rust",]
cli-docs = []
svgscreenshot = ["svg_crate"]
gpgme = ["melib/gpgme"]
# Print tracing logs as meli runs in stderr
# enable for debug tracing logs: build with --features=debug-tracing

3
melib/Cargo.toml

@ -56,9 +56,9 @@ default = ["unicode_algorithms", "imap_backend", "maildir_backend", "mbox_backen
debug-tracing = []
deflate_compression = ["flate2", ]
gpgme = []
http = ["isahc"]
http-static = ["isahc", "isahc/static-curl"]
tls = ["native-tls"]
imap_backend = ["tls"]
jmap_backend = ["http", "serde_json"]
maildir_backend = ["notify", "memmap"]
@ -66,5 +66,6 @@ mbox_backend = ["notify", "memmap"]
notmuch_backend = []
smtp = ["tls", "base64"]
sqlite3 = ["rusqlite", ]
tls = ["native-tls"]
unicode_algorithms = ["unicode-segmentation"]
vcard = []

3
melib/src/email.rs

@ -97,7 +97,7 @@ pub mod headers;
pub mod list_management;
pub mod mailto;
pub mod parser;
pub mod signatures;
pub mod pgp;
pub use address::{Address, MessageID, References, StrBuild, StrBuilder};
pub use attachments::{Attachment, AttachmentBuilder};
@ -219,6 +219,7 @@ impl core::fmt::Debug for Envelope {
.field("Message-ID", &self.message_id_display())
.field("In-Reply-To", &self.in_reply_to_display())
.field("References", &self.references)
.field("Flags", &self.flags)
.field("Hash", &self.hash)
.finish()
}

90
melib/src/email/attachment_types.rs

@ -135,6 +135,7 @@ impl Display for Charset {
pub enum MultipartType {
Alternative,
Digest,
Encrypted,
Mixed,
Related,
Signed,
@ -148,13 +149,18 @@ impl Default for MultipartType {
impl Display for MultipartType {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
match self {
MultipartType::Alternative => write!(f, "multipart/alternative"),
MultipartType::Digest => write!(f, "multipart/digest"),
MultipartType::Mixed => write!(f, "multipart/mixed"),
MultipartType::Related => write!(f, "multipart/related"),
MultipartType::Signed => write!(f, "multipart/signed"),
}
write!(
f,
"{}",
match self {
MultipartType::Alternative => "multipart/alternative",
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
} else if val.eq_ignore_ascii_case(b"digest") {
MultipartType::Digest
} else if val.eq_ignore_ascii_case(b"encrypted") {
MultipartType::Encrypted
} else if val.eq_ignore_ascii_case(b"signed") {
MultipartType::Signed
} 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 {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
match self {

140
melib/src/email/attachments.rs

@ -351,16 +351,14 @@ pub struct Attachment {
impl fmt::Debug for Attachment {
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}}",
self.content_type,
self.content_transfer_encoding,
self.raw.len(),
{
let mut text = Vec::with_capacity(4096);
self.get_text_recursive(&mut text);
std::str::from_utf8(&text).map(std::string::ToString::to_string).unwrap_or_else(|e| format!("Unicode error {}", e))
}
)
let mut text = Vec::with_capacity(4096);
self.get_text_recursive(&mut text);
f.debug_struct("Attachment")
.field("content_type", &self.content_type)
.field("content_transfer_encoding", &self.content_transfer_encoding)
.field("raw bytes length", &self.raw.len())
.field("body", &String::from_utf8_lossy(&text))
.finish()
}
}
@ -371,44 +369,63 @@ impl fmt::Display for Attachment {
match Mail::new(self.body.display_bytes(&self.raw).to_vec(), None) {
Ok(wrapper) => write!(
f,
"message/rfc822: {} - {} - {}",
"{} - {} - {} [message/rfc822] {}",
wrapper.date(),
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::OctetStream { ref name } => {
write!(f, "{}", name.clone().unwrap_or_else(|| self.mime_type()))
ContentType::PGPSignature => write!(f, "pgp signature [{}]", self.mime_type()),
ContentType::OctetStream { .. } | ContentType::Other { .. } => {
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 {
name: Some(ref name),
..
} => write!(f, "\"{}\", [{}]", name, self.mime_type()),
ContentType::Other { .. } => write!(f, "Data attachment of type {}", self.mime_type()),
ContentType::Text { ref parameters, .. }
if parameters
.iter()
.any(|(name, _)| name.eq_ignore_ascii_case(b"name")) =>
{
let name = String::from_utf8_lossy(
parameters
.iter()
.find(|(name, _)| name.eq_ignore_ascii_case(b"name"))
.map(|(_, value)| value)
.unwrap(),
);
write!(f, "\"{}\", [{}]", name, self.mime_type())
ContentType::Text { .. } => {
if let Some(name) = self.filename() {
write!(
f,
"\"{}\", [{}] {}",
name,
self.mime_type(),
crate::Bytes(self.raw.len())
)
} else {
write!(
f,
"Text attachment [{}] {}",
self.mime_type(),
crate::Bytes(self.raw.len())
)
}
}
ContentType::Text { .. } => write!(f, "Text attachment of type {}", self.mime_type()),
ContentType::Multipart {
parts: ref sub_att_vec,
..
} => write!(
f,
"{} attachment with {} subs",
"{} attachment with {} parts",
self.mime_type(),
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 {
match self.content_type {
ContentType::Multipart {
@ -641,7 +668,7 @@ impl Attachment {
let mut ret = String::with_capacity(2 * self.raw.len());
fn into_raw_helper(a: &Attachment, ret: &mut String) {
ret.push_str(&format!(
"Content-Transfer-Encoding: {}\n",
"Content-Transfer-Encoding: {}\r\n",
a.content_transfer_encoding
));
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()));
}
ContentType::Multipart {
@ -680,36 +707,36 @@ impl Attachment {
if *kind == MultipartType::Signed {
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 {
ret.push_str(&boundary_start);
into_raw_helper(p, ret);
}
ret.push_str(&format!("--{}--\n\n", boundary));
ret.push_str(&format!("--{}--\r\n\r\n", boundary));
}
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()));
}
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()));
}
ContentType::OctetStream { ref name } => {
if let Some(name) = name {
ret.push_str(&format!(
"Content-Type: {}; name={}\n\n",
"Content-Type: {}; name={}\r\n\r\n",
a.content_type, name
));
} 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(&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()));
}
}
@ -752,9 +779,18 @@ impl Attachment {
h.eq_ignore_ascii_case(b"name") | h.eq_ignore_ascii_case(b"filename")
})
.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,
})
.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(), "_"))
}
}
@ -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
}
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();
for a in parts {

21
melib/src/email/signatures.rs → melib/src/email/pgp.rs

@ -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,
}

10965
melib/src/gpgme/bindings.rs
File diff suppressed because it is too large
View File

155
melib/src/gpgme/io.rs

@ -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())
}
}
}

798
melib/src/gpgme/mod.rs

@ -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());