#![allow(dead_code)]
use std::{
env, fs,
io::prelude::*,
path::Path,
process::{Command, Stdio},
sync::{
atomic::{AtomicUsize, Ordering},
RwLock,
},
};
use gpgme::{self, Context, PassphraseRequest, PinentryMode};
use tempfile::TempDir;
macro_rules! count {
() => {0usize};
($_head:tt $($tail:tt)*) => {1usize + count!($($tail)*)};
}
macro_rules! test_case {
(@impl $name:ident($tester:ident) $body:block) => {
#[test]
fn $name() {
let $tester = TEST_CASE.new_test();
$body
}
};
(@impl #[requires($version:tt)] $name:ident($tester:ident) $body:block) => {
test_case!(@impl $name($tester) {
#[cfg(feature = $version)] {
$body
}
});
};
($($(#[requires($version:tt)])? $name:ident($tester:ident) $body:block)+) => {
static TEST_CASE: ::once_cell::sync::Lazy<$crate::common::TestCase>
= ::once_cell::sync::Lazy::new(|| $crate::common::TestCase::new(count!($($name)+)));
$(test_case!(@impl $(#[requires($version)])* $name($tester) $body);)+
};
}
pub fn passphrase_cb(_req: PassphraseRequest<'_>, out: &mut dyn Write) -> gpgme::Result<()> {
out.write_all(b"abc")?;
Ok(())
}
fn import_key(key: &[u8]) {
let gpg = env::var_os("GPG").unwrap_or("gpg".into());
let mut child = Command::new(&gpg)
.arg("--no-permission-warning")
.arg("--passphrase")
.arg("abc")
.arg("--import")
.stdin(Stdio::piped())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.unwrap();
child.stdin.as_mut().unwrap().write_all(key).unwrap();
assert!(child.wait().unwrap().success());
}
fn setup_agent(dir: &Path) {
env::set_var("GNUPGHOME", dir);
env::set_var("GPG_AGENT_INFO", "");
let pinentry = Path::new(env!("CARGO_BIN_EXE_pinentry"));
if !pinentry.exists() {
panic!("Unable to find pinentry program");
}
let conf = dir.join("gpg.conf");
fs::write(
conf,
"ignore-invalid-option allow-weak-key-signatures\n\
allow-weak-key-signatures\n",
)
.unwrap();
let agent_conf = dir.join("gpg-agent.conf");
fs::write(
agent_conf,
format!(
"ignore-invalid-option allow-loopback-pinentry\n\
allow-loopback-pinentry\n\
ignore-invalid-option pinentry-mode\n\
pinentry-mode loopback\n\
pinentry-program {}\n",
pinentry.to_str().unwrap()
),
)
.unwrap();
}
pub struct TestCase {
count: AtomicUsize,
homedir: RwLock<Option<TempDir>>,
}
impl TestCase {
pub fn new(count: usize) -> TestCase {
let dir = TempDir::new().unwrap();
setup_agent(dir.path());
import_key(include_bytes!("./data/pubdemo.asc"));
import_key(include_bytes!("./data/secdemo.asc"));
TestCase {
count: AtomicUsize::new(count),
homedir: RwLock::new(Some(dir)),
}
}
pub fn new_test(&self) -> Test<'_> {
Test { parent: self }
}
pub fn kill_agent(&self) {
let socket = {
let homedir = self.homedir.read().unwrap();
homedir.as_ref().unwrap().path().join("S.gpg-agent")
};
let mut child = match Command::new("gpg-connect-agent")
.arg("-S")
.arg(socket)
.stdin(Stdio::piped())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
{
Ok(child) => child,
Err(err) => {
println!("Unable to kill agent: {}", err);
return;
}
};
if let Some(ref mut stdin) = child.stdin {
let _ = stdin.write_all(b"KILLAGENT\nBYE\n");
let _ = stdin.flush();
}
if let Err(err) = child.wait() {
println!("Unable to kill agent: {}", err);
}
}
fn drop(&self) {
if self.count.fetch_sub(1, Ordering::SeqCst) == 1 {
self.kill_agent();
self.homedir.write().unwrap().take();
}
}
}
pub struct Test<'a> {
parent: &'a TestCase,
}
impl Drop for Test<'_> {
fn drop(&mut self) {
self.parent.drop();
}
}
impl Test<'_> {
pub fn create_context(&self) -> Context {
let mut ctx = Context::from_protocol(gpgme::Protocol::OpenPgp).unwrap();
let _ = ctx.set_pinentry_mode(PinentryMode::Loopback);
ctx
}
}