//! GnuPG RPC support.
#![warn(missing_docs)]
use std::collections::BTreeMap;
use std::convert::TryFrom;
use std::ffi::OsStr;
use std::ops::{Deref, DerefMut};
use std::path::{Path, PathBuf};
use futures::{Stream, StreamExt};
use std::task::{Poll, self};
use std::pin::Pin;
use sequoia_openpgp as openpgp;
use openpgp::types::{HashAlgorithm, Timestamp};
use openpgp::fmt::hex;
use openpgp::cert::ValidCert;
use openpgp::crypto;
use openpgp::packet::prelude::*;
use openpgp::parse::Parse;
use crate::Result;
use crate::assuan;
use crate::Keygrip;
use crate::sexp::Sexp;
/// A GnuPG context.
#[derive(Debug)]
pub struct Context {
homedir: Option<PathBuf>,
sockets: BTreeMap<String, PathBuf>,
ephemeral: Option<tempfile::TempDir>,
// XXX: Remove me once hack for Cygwin won't be necessary.
#[cfg(windows)]
cygwin: bool,
}
impl Context {
/// Creates a new context for the default GnuPG home directory.
pub fn new() -> Result<Self> {
Self::make(None, None)
}
/// Creates a new context for the given GnuPG home directory.
pub fn with_homedir<P>(homedir: P) -> Result<Self>
where P: AsRef<Path>
{
Self::make(Some(homedir.as_ref()), None)
}
/// Creates a new ephemeral context.
///
/// The created home directory will be deleted once this object is
/// dropped.
pub fn ephemeral() -> Result<Self> {
Self::make(None, Some(tempfile::tempdir()?))
}
fn make(homedir: Option<&Path>, ephemeral: Option<tempfile::TempDir>)
-> Result<Self> {
let mut sockets: BTreeMap<String, PathBuf> = Default::default();
let ephemeral_dir = ephemeral.as_ref().map(|tmp| tmp.path());
let homedir = ephemeral_dir.or(homedir);
// Guess if we're dealing with Unix/Cygwin or native Windows variant
// We need to do that in order to pass paths in correct style to gpgconf
let a_gpg_path = Self::gpgconf(&None, &["--list-dirs", "homedir"], 1)?;
let first_byte = a_gpg_path.get(0).and_then(|c| c.get(0)).and_then(|c| c.get(0));
let gpg_style = match first_byte {
Some(b'/') => Mode::Unix,
_ => Mode::native(),
};
let homedir = homedir.map(|dir|
convert_path(dir, gpg_style)
.unwrap_or_else(|_| PathBuf::from(dir))
);
for fields in Self::gpgconf(&homedir, &["--list-dirs"], 2)? {
let key = std::str::from_utf8(&fields[0])?;
// For now, we're only interested in sockets.
let socket = match key.strip_suffix("-socket") {
Some(socket) => socket,
_ => continue,
};
// NOTE: Directories and socket paths are percent-encoded if no
// argument to "--list-dirs" is given
let mut value = std::str::from_utf8(&fields[1])?.to_owned();
// FIXME: Percent-decode everything, but for now at least decode
// colons to support Windows drive letters
value = value.replace("%3a", ":");
// Store paths in native format, following the least surprise rule.
let path = convert_path(&value, Mode::native())?;
sockets.insert(socket.into(), path);
}
/// Whether we're dealing with gpg that expects Windows or Unix-style paths.
#[derive(Copy, Clone)]
#[allow(dead_code)]
enum Mode {
Windows,
Unix
}
impl Mode {
fn native() -> Self {
platform! {
unix => Mode::Unix,
windows => Mode::Windows,
}
}
}
#[cfg(not(windows))]
fn convert_path(path: impl AsRef<OsStr>, mode: Mode) -> Result<PathBuf> {
match mode {
Mode::Unix => Ok(PathBuf::