use crate::email::{
pgp::{DecryptionMetadata, Recipient},
Address,
};
use crate::error::{ErrorKind, IntoMeliError, MeliError, Result, ResultIntoMeliError};
use futures::FutureExt;
use serde::{
de::{self, Deserialize},
Deserializer, Serialize, Serializer,
};
use smol::Async;
use std::borrow::Cow;
use std::collections::HashMap;
use std::ffi::{CStr, CString, OsStr};
use std::future::Future;
use std::io::Seek;
use std::os::unix::ffi::OsStrExt;
use std::os::unix::io::{AsRawFd, RawFd};
use std::path::Path;
use std::sync::{Arc, Mutex};
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
}};
}
macro_rules! c_string_literal {
($lit:literal) => {{
unsafe {
CStr::from_bytes_with_nul_unchecked(concat!($lit, "\0").as_bytes()).as_ptr()
as *const ::std::os::raw::c_char
}
}};
}
mod bindings;
use bindings::*;
mod io;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum GpgmeFlag {
AutoKeyRetrieve,
OfflineMode,
AsciiArmor,
}
bitflags! {
pub struct LocateKey: u8 {
const CERT = 0b1;
const PKA = 0b10;
const DANE = 0b100;
const WKD = 0b1000;
const LDAP = 0b10000;
const KEYSERVER = 0b100000;
const KEYSERVER_URL = 0b1000000;
const LOCAL = 0b10000000;
const NODEFAULT = 0;
}
}
impl<'de> Deserialize<'de> for LocateKey {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
if let Ok(s) = <String>::deserialize(deserializer) {
LocateKey::from_string_de::<'de, D, String>(s)
} else {
Err(de::Error::custom("LocateKey value must be a string."))
}
}
}
impl Serialize for LocateKey {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl LocateKey {
pub fn from_string_de<'de, D, T: AsRef<str>>(s: T) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Ok(match s.as_ref().trim() {
s if s.eq_ignore_ascii_case("cert") => LocateKey::CERT,
s if s.eq_ignore_ascii_case("pka") => LocateKey::PKA,
s if s.eq_ignore_ascii_case("dane") => LocateKey::DANE,
s if s.eq_ignore_ascii_case("wkd") => LocateKey::WKD,
s if s.eq_ignore_ascii_case("ldap") => LocateKey::LDAP,
s if s.eq_ignore_ascii_case("keyserver") => LocateKey::KEYSERVER,
s if s.eq_ignore_ascii_case("keyserver-url") => LocateKey::KEYSERVER_URL,
s if s.eq_ignore_ascii_case("local") => LocateKey::LOCAL,
combination if combination.contains(",") => {
let mut ret = LocateKey::NODEFAULT;
for c in combination.trim().split(",") {
ret |= Self::from_string_de::<'de, D, &str>(c.trim())?;
}
ret
}
_ => {
return Err(de::Error::custom(
r#"Takes valid auto-key-locate GPG values: "cert", "pka", "dane", "wkd", "ldap", "keyserver", "keyserver-URL", "local", "nodefault""#,
))
}
})
}
}
impl std::fmt::Display for LocateKey {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
if *self == LocateKey::NODEFAULT {
write!(fmt, "clear,nodefault")
} else {
let mut accum = String::new();
macro_rules! is_set {
($flag:expr, $string:literal) => {{
if self.intersects($flag) {
accum.push_str($string);
accum.push_str(",");
}
}};
}
is_set!(LocateKey::CERT, "cert");
is_set!(LocateKey::PKA, "pka");
is_set!(LocateKey::WKD, "wkd");
is_set!(LocateKey::LDAP, "ldap");
is_set!(LocateKey::KEYSERVER, "keyserver");
is_set!(LocateKey::KEYSERVER_URL, "keyserver-url");
is_set!(LocateKey::LOCAL, "local");
accum.pop();
write!(fmt, "{}", accum)
}
}
}
struct IoState {
max_idx: usize,
ops: HashMap<usize, GpgmeFd>,
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 lib = Arc::new(libloading::Library::new(libloading::library_filename(
"gpgme",
))?);
if unsafe { call!(&lib, gpgme_check_version)(GPGME_VERSION.as_bytes().as_ptr() as *mut _) }
.is_null()
{
return Err(MeliError::new(format!(
"Could not use libgpgme: requested version compatible with {} but got {}",
GPGME_VERSION,
unsafe {
CStr::from_ptr(call!(&lib, gpgme_check_version)(std::ptr::null_mut()))
.to_string_lossy()
},
))
.set_kind(ErrorKind::External));
};
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,
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,
};
unsafe {
gpgme_error_try(&lib, call!(&lib, gpgme_new)(&mut ptr))?;
call!(&lib, gpgme_set_io_cbs)(ptr, &mut io_cbs);
}
let ret = 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,
};
ret.set_flag(GpgmeFlag::AutoKeyRetrieve, false)?;
ret.set_flag(GpgmeFlag::OfflineMode, true)?;
ret.set_flag(GpgmeFlag::AsciiArmor, true)?;
ret.set_auto_key_locate(LocateKey::LOCAL)?;
Ok(ret)
}
fn set_flag_inner(
&self,
raw_flag: *const ::std::os::raw::c_char,
raw_value: *const ::std::os::raw::c_char,
) -> Result<()> {
unsafe {
gpgme_error_try(
&self.inner.lib,
call!(&self.inner.lib, gpgme_set_ctx_flag)(
self.inner.inner.as_ptr(),
raw_flag,
raw_value,
),
)?;
}
Ok(())
}
pub fn set_flag(&self, flag: GpgmeFlag, value: bool) -> Result<()> {
match flag {
GpgmeFlag::AutoKeyRetrieve => {}
GpgmeFlag::OfflineMode => {
unsafe {
call!(&self.inner.lib, gpgme_set_offline)(
self.inner.inner.as_ptr(),
if value { 1 } else { 0 },
);
};
return Ok(());
}
GpgmeFlag::AsciiArmor => {
unsafe {
call!(&self.inner.lib, gpgme_set_armor)(
self.inner.inner.as_ptr(),
if value { 1 } else { 0 },
);
};
return Ok(());
}
};
const VALUE_ON: &[u8; 2] = b"1\0";
const VALUE_OFF: &[u8; 2] = b"0\0";
let raw_flag = match flag {
GpgmeFlag::AutoKeyRetrieve => c_string_literal!("auto-key-retrieve"),
GpgmeFlag::AsciiArmor | GpgmeFlag::OfflineMode => unreachable!(),
};
self.set_flag_inner(
raw_flag,
if value { VALUE_ON } else { VALUE_OFF }.as_ptr() as *const ::std::os::raw::c_char,
)
}
fn get_flag_inner(
&self,
raw_flag: *const ::std::os::raw::c_char,
) -> *const ::std::os::raw::c_char {
unsafe { call!(&self.inner.lib, gpgme_get_ctx_flag)(self.inner.inner.as_ptr(), raw_flag) }
}
pub fn get_flag(&self, flag: GpgmeFlag) -> Result<bool> {
let raw_flag = match flag {
GpgmeFlag::AutoKeyRetrieve => c_string_literal!("auto-key-retrieve"),
GpgmeFlag::OfflineMode => {
return Ok(unsafe {
call!(&self.inner.lib, gpgme_get_offline)(self.inner.inner.as_ptr()) > 0
});
}
GpgmeFlag::AsciiArmor => {
return Ok(unsafe {
call!(&self.inner.lib, gpgme_get_armor)(self.inner.inner.as_ptr()) > 0
});
}
};
let val = self.get_flag_inner(raw_flag);
Ok(!val.is_null())
}
pub fn set_auto_key_locate(&self, val: LocateKey) -> Result<()> {
let auto_key_locate: *const ::std::os::raw::c_char = c_string_literal!("auto-key-locate");
if val == LocateKey::NODEFAULT {
self.set_flag_inner(auto_key_locate, c_string_literal!("clear,nodefault"))
} else {
let mut accum = val.to_string();
accum.push('\0');
self.set_flag_inner(
auto_key_locate,
CStr::from_bytes_with_nul(accum.as_bytes())
.expect(accum.as_str())
.as_ptr() as *const _,
)
}
}
pub fn get_auto_key_locate(&self) -> Result<LocateKey> {
let auto_key_locate: *const ::std::os::raw::c_char = c_string_literal!("auto-key-locate");
let raw_value =
unsafe { CStr::from_ptr(self.get_flag_inner(auto_key_locate)) }.to_string_lossy();
let mut val = LocateKey::NODEFAULT;
if !raw_value.contains("nodefault") {
for mechanism in raw_value.split(",") {
match mechanism {
"cert" => val.set(LocateKey::CERT, true),
"pka" => {
val.set(LocateKey::PKA, true);
}
"wkd" => {
val.set(LocateKey::WKD, true);
}
"ldap" => {
val.set(LocateKey::LDAP, true);
}
"keyserver" => {
val.set(LocateKey::KEYSERVER, true);
}
"keyserver-url" => {
val.set(LocateKey::KEYSERVER_URL, true);
}
"local" => {
val.set(LocateKey::LOCAL, true);
}
unknown => {
debug!("unknown mechanism: {}", unknown);
}
}
}
}
Ok(val)
}
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,
secret: bool,
pattern: Option<String>,
) -> Result<impl Future<Output = Result<Vec<Key>>>> {
let pattern = if let Some(pattern) = pattern {
Some(CString::new(pattern)?)
} else {
None
};
unsafe {
gpgme_error_try(
&self.inner.lib,
call!(&self.inner.lib, gpgme_op_keylist_start)(
self.inner.inner.as_ptr(),
pattern
.as_ref()
.map(|cs| cs.as_ptr())
.unwrap_or(std::ptr::null_mut())
as *const ::std::os::raw::c_char,
if secret { 1 } else { 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(inner) = key_receiver.try_recv() {
let key = Key::new(inner, ctx.lib.clone());
keys.push(key);
}
Ok(keys)
})
}
pub fn sign(
&mut self,
sign_keys: Vec<Key>,
mut text: Data,
) -> Result<impl Future<Output = Result<Vec<u8>>>> {
if sign_keys.is_empty() {
return Err(
MeliError::new("gpgme: Call to sign() with zero keys.").set_kind(ErrorKind::Bug)
);
}
let mut sig: gpgme_data_t = std::ptr::null_mut();
unsafe {
gpgme_error_try(
&self.inner.lib,
call!(&self.inner.lib, gpgme_data_new)(&mut sig),
)?;
call!(&self.inner.lib, gpgme_signers_clear)(self.inner.inner.as_ptr());
for k in sign_keys {
gpgme_error_try(
&self.inner.lib,
call!(&self.inner.lib, gpgme_signers_add)(
self.inner.inner.as_ptr(),
k.inner.inner.as_ptr(),
),
)?;
}
}
unsafe {
gpgme_error_try(
&self.inner.lib,
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 mut sig = Data {
lib: self.inner.lib.clone(),
kind: DataKind::Memory,
inner: core::ptr::NonNull::new(sig).ok_or_else(|| {
MeliError::new("internal libgpgme error").set_kind(ErrorKind::Bug)
})?,
};
let io_state = self.io_state.clone();
let io_state_lck = self.io_state.lock().unwrap();
let done = io_state_lck.done.clone();
let fut = io_state_lck
.ops
.values()
.map(|a| Async::new(a.clone()).unwrap())
.collect::<Vec<Async<GpgmeFd>>>();
drop(io_state_lck);
Ok(async move {
futures::future::join_all(fut.iter().map(|fut| {
let done = done.clone();
if fut.get_ref().write {
futures::future::select(
fut.get_ref().receiver.recv().boxed(),
fut.write_with(move |_f| {
if done.lock().unwrap().is_some() {
return Ok(());
}
unsafe {
(fut.get_ref().fnc.unwrap())(
fut.get_ref().fnc_data,
fut.get_ref().fd,
)
};
if done.lock().unwrap().is_none() {
return Err(std::io::ErrorKind::WouldBlock.into());
}
Ok(())
})
.boxed(),
)
.boxed()
} else {
futures::future::select(
fut.get_ref().receiver.recv().boxed(),
fut.read_with(move |_f| {
if done.lock().unwrap().is_some() {
return Ok(());
}
unsafe {
(fut.get_ref().fnc.unwrap())(
fut.get_ref().fnc_data,
fut.get_ref().fd,
)
};
if done.lock().unwrap().is_none() {
return Err(std::io::ErrorKind::WouldBlock.into());
}
Ok(())
})
.boxed(),
)
.boxed()
}
}))
.await;
let rcv = {
let io_state_lck = io_state.lock().unwrap();
io_state_lck.receiver.clone()
};
let _ = rcv.recv().await;
let io_state_lck = io_state.lock().unwrap();
io_state_lck
.done
.lock()
.unwrap()
.take()
.unwrap_or_else(|| Err(MeliError::new("Unspecified libgpgme error")))?;
sig.seek(std::io::SeekFrom::Start(0))
.chain_err_summary(|| {
"libgpgme error: could not perform seek on signature data object"
})?;
Ok(sig.into_bytes()?)
})
}
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;
}
}
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()?,
))
})
}
pub fn encrypt(
&mut self,
sign_keys: Option<Vec<Key>>,
encrypt_keys: Vec<Key>,
mut plain: Data,
) -> Result<impl Future<Output = Result<Vec<u8>>> + Send> {
if encrypt_keys.is_empty() {
return Err(
MeliError::new("gpgme: Call to encrypt() with zero keys.").set_kind(ErrorKind::Bug)
);
}
unsafe {
call!(&self.inner.lib, gpgme_signers_clear)(self.inner.inner.as_ptr());
}
let also_sign: bool = if let Some(keys) = sign_keys {
if keys.is_empty() {
false
} else {
for k in keys {
unsafe {
gpgme_error_try(
&self.inner.lib,
call!(&self.inner.lib, gpgme_signers_add)(
self.inner.inner.as_ptr(),
k.inner.inner.as_ptr(),
),
)?;
}
}
true
}
} else {
false
};
let mut cipher: gpgme_data_t = std::ptr::null_mut();
let mut raw_keys: Vec<gpgme_key_t> = Vec::with_capacity(encrypt_keys.len() + 1);
raw_keys.extend(encrypt_keys.iter().map(|k| k.inner.inner.as_ptr()));
raw_keys.push(std::ptr::null_mut());
unsafe {
gpgme_error_try(
&self.inner.lib,
call!(&self.inner.lib, gpgme_data_new)(&mut cipher),
)?;
gpgme_error_try(
&self.inner.lib,
if also_sign {
call!(&self.inner.lib, gpgme_op_encrypt_sign_start)(
self.inner.inner.as_ptr(),
raw_keys.as_mut_ptr(),
gpgme_encrypt_flags_t_GPGME_ENCRYPT_NO_ENCRYPT_TO
| gpgme_encrypt_flags_t_GPGME_ENCRYPT_NO_COMPRESS,
plain.inner.as_mut(),
cipher,
)
} else {
call!(&self.inner.lib, gpgme_op_encrypt_start)(
self.inner.inner.as_ptr(),
raw_keys.as_mut_ptr(),
gpgme_encrypt_flags_t_GPGME_ENCRYPT_NO_ENCRYPT_TO
| gpgme_encrypt_flags_t_GPGME_ENCRYPT_NO_COMPRESS,
plain.inner.as_mut(),
cipher,
)
},
)?;
}
let mut cipher = Data {
lib: self.inner.lib.clone(),
kind: DataKind::Memory,
inner: core::ptr::NonNull::new(cipher).ok_or_else(|| {
MeliError::new("internal libgpgme error").set_kind(ErrorKind::Bug)
})?,
};
let ctx = self.inner.clone();
let io_state = self.io_state.clone();
let io_state_lck = self.io_state.lock().unwrap();
let done = io_state_lck.done.clone();
let fut = io_state_lck
.ops
.values()
.map(|a| Async::new(a.clone()).unwrap())
.collect::<Vec<Async<GpgmeFd>>>();
drop(io_state_lck);
Ok(async move {
futures::future::join_all(fut.iter().map(|fut| {
let done = done.clone();
if fut.get_ref().write {
futures::future::select(
fut.get_ref().receiver.recv().boxed(),
fut.write_with(move |_f| {
if done.lock().unwrap().is_some() {
return Ok(());
}
unsafe {
(fut.get_ref().fnc.unwrap())(
fut.get_ref().fnc_data,
fut.get_ref().fd,
)
};
if done.lock().unwrap().is_none() {
return Err(std::io::ErrorKind::WouldBlock.into());
}
Ok(())
})
.boxed(),
)
.boxed()
} else {
futures::future::select(
fut.get_ref().receiver.recv().boxed(),
fut.read_with(move |_f| {
if done.lock().unwrap().is_some() {
return Ok(());
}
unsafe {
(fut.get_ref().fnc.unwrap())(
fut.get_ref().fnc_data,
fut.get_ref().fd,
)
};
if done.lock().unwrap().is_none() {
return Err(std::io::ErrorKind::WouldBlock.into());
}
Ok(())
})
.boxed(),
)
.boxed()
}
}))
.await;
let rcv = {
let io_state_lck = io_state.lock().unwrap();
io_state_lck.receiver.clone()
};
let _ = rcv.recv().await;
let io_state_lck = io_state.lock().unwrap();
io_state_lck
.done
.lock()
.unwrap()
.take()
.unwrap_or_else(|| Err(MeliError::new("Unspecified libgpgme error")))?;
let encrypt_result =
unsafe { call!(&ctx.lib, gpgme_op_encrypt_result)(ctx.inner.as_ptr()) };
if encrypt_result.is_null() {
return Err(MeliError::new(
"Unspecified libgpgme error: gpgme_op_encrypt_result returned NULL.",
)
.set_err_kind(ErrorKind::External));
}
cipher
.seek(std::io::SeekFrom::Start(0))
.chain_err_summary(|| "libgpgme error: could not perform seek on plain text")?;
Ok(cipher.into_bytes()?)
})
}
}
fn gpgme_error_try(lib: &libloading::Library, error_code: GpgmeError) -> Result<()> {
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 Key {
#[inline(always)]
fn new(inner: KeyInner, lib: Arc<libloading::Library>) -> Self {
Key { inner, lib }
}
pub fn primary_uid(&self) -> Option<Address> {
unsafe {
if (*(self.inner.inner.as_ptr())).uids.is_null() {
return None;
}
let uid = (*(self.inner.inner.as_ptr())).uids;
if (*uid).name.is_null() && (*uid).email.is_null() {
None
} else if (*uid).name.is_null() {
Some(Address::new(
None,
CStr::from_ptr((*uid).email).to_string_lossy().to_string(),
))
} else if (*uid).email.is_null() {
Some(Address::new(
None,
CStr::from_ptr((*uid).name).to_string_lossy().to_string(),
))
} else {
Some(Address::new(
Some(CStr::from_ptr((*uid).name).to_string_lossy().to_string()),
CStr::from_ptr((*uid).email).to_string_lossy().to_string(),
))
}
}
}
pub fn revoked(&self) -> bool {
unsafe { (*self.inner.inner.as_ptr()).revoked() > 0 }
}
pub fn expired(&self) -> bool {
unsafe { (*self.inner.inner.as_ptr()).expired() > 0 }
}
pub fn disabled(&self) -> bool {
unsafe { (*self.inner.inner.as_ptr()).disabled() > 0 }
}
pub fn invalid(&self) -> bool {
unsafe { (*self.inner.inner.as_ptr()).invalid() > 0 }
}
pub fn can_encrypt(&self) -> bool {
unsafe { (*self.inner.inner.as_ptr()).can_encrypt() > 0 }
}
pub fn can_sign(&self) -> bool {
unsafe { (*self.inner.inner.as_ptr()).can_sign() > 0 }
}
pub fn secret(&self) -> bool {
unsafe { (*self.inner.inner.as_ptr()).secret() > 0 }
}
pub fn fingerprint(&self) -> Cow<'_, str> {
(unsafe { CStr::from_ptr((*(self.inner.inner.as_ptr())).fpr) }).to_string_lossy()
}
}
impl std::fmt::Debug for Key {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
fmt.debug_struct("Key")
.field("fingerprint", &self.fingerprint())
.field("uid", &self.primary_uid())
.field("can_encrypt", &self.can_encrypt())
.field("can_sign", &self.can_sign())
.field("secret", &self.secret())
.field("revoked", &self.revoked())
.field("expired", &self.expired())
.field("invalid", &self.invalid())
.finish()
}
}
impl std::cmp::PartialEq for Key {
fn eq(&self, other: &Key) -> bool {
self.fingerprint() == other.fingerprint()
}
}
impl Drop for Key {
#[inline]
fn drop(&mut self) {
unsafe {
call!(&self.lib, gpgme_key_unref)(self.inner.inner.as_ptr());
}
}
}