#![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>, } 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 } }