diff options
author | Igor Matuszewski <igor@sequoia-pgp.org> | 2020-10-25 00:33:00 +0200 |
---|---|---|
committer | Igor Matuszewski <igor@sequoia-pgp.org> | 2020-11-01 19:13:33 +0100 |
commit | c392cc06db1fa132a5b3ddd380667bddb4e08012 (patch) | |
tree | e837948c4927e7a893f4748a7b8f289c9ad43750 /ipc | |
parent | a0da43702413524e4a8a37def96fea0645e10cd8 (diff) |
ipc: Support GnuPG on Cygwin/MinGW
Diffstat (limited to 'ipc')
-rw-r--r-- | ipc/src/gnupg.rs | 97 | ||||
-rw-r--r-- | ipc/tests/gpg-agent.rs | 2 |
2 files changed, 95 insertions, 4 deletions
diff --git a/ipc/src/gnupg.rs b/ipc/src/gnupg.rs index ee355319..4cf11979 100644 --- a/ipc/src/gnupg.rs +++ b/ipc/src/gnupg.rs @@ -4,6 +4,7 @@ use std::collections::BTreeMap; use std::convert::TryFrom; +use std::ffi::OsStr; use std::ops::{Deref, DerefMut}; use std::path::{Path, PathBuf}; use std::process::Command; @@ -35,6 +36,9 @@ pub struct Context { sockets: BTreeMap<String, PathBuf>, #[allow(dead_code)] // We keep it around for the cleanup. ephemeral: Option<tempfile::TempDir>, + // XXX: Remove me once hack for Cygwin won't be necessary. + #[cfg(windows)] + cygwin: bool, } impl Context { @@ -65,7 +69,19 @@ impl Context { let mut sockets: BTreeMap<String, PathBuf> = Default::default(); let ephemeral_dir = ephemeral.as_ref().map(|tmp| tmp.path()); - let homedir = ephemeral_dir.or(homedir).map(PathBuf::from); + 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(|l| l.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-components"], 3)? { components.insert(String::from_utf8(fields[0].clone())?, @@ -80,19 +96,72 @@ impl Context { // 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())?; match key.strip_suffix("-socket") { - None => directories.insert(key.into(), value.into()), - Some(key) => sockets.insert(key.into(), value.into()), + None => directories.insert(key.into(), path), + Some(key) => sockets.insert(key.into(), path), }; } + /// Whether we're dealing with gpg that expects Windows or Unix-style paths. + #[derive(Copy, Clone)] + enum Mode { + Windows, + Unix + } + + impl Mode { + fn native() -> Self { + match () { + _ if cfg!(windows) => Mode::Windows, + _ if cfg!(unix) => Mode::Unix, + _ => unimplemented!(), + } + } + } + + #[cfg(not(windows))] + fn convert_path(path: impl AsRef<OsStr>, mode: Mode) -> Result<PathBuf> { + match mode { + Mode::Unix => Ok(PathBuf::from(path.as_ref())), + Mode::Windows => Err(anyhow::anyhow!( + "Converting to Windows-style paths is only supported on Windows" + )), + } + } + + #[cfg(windows)] + fn convert_path(path: impl AsRef<OsStr>, mode: Mode) -> Result<PathBuf> { + let conversion_type = match mode { + Mode::Windows => "--windows", + Mode::Unix => "--unix", + }; + Command::new("cygpath").arg(conversion_type).arg(path.as_ref()) + .output() + .map_err(Into::into) + .and_then(|out| + if out.status.success() { + let output = std::str::from_utf8(&out.stdout)?.trim(); + Ok(PathBuf::from(output)) + } else { + Err(anyhow::anyhow!( + "Executing cygpath encountered error for path {}", + path.as_ref().to_string_lossy() + )) + } + ) + } + Ok(Context { homedir, components, directories, sockets, ephemeral, + #[cfg(windows)] + cygwin: cfg!(windows) && matches!(gpg_style, Mode::Unix), }) } @@ -147,6 +216,16 @@ impl Context { } } + /// Returns the path to `homedir` directory. + /// + /// The path returned will be in a local format, i. e. one accepted by + /// available `gpgconf` or `gpg` tools. + /// + /// + pub fn homedir(&self) -> Option<&Path> { + self.homedir.as_deref() + } + /// Returns the path to a GnuPG component. pub fn component<C>(&self, component: C) -> Result<&Path> where C: AsRef<str> @@ -185,6 +264,18 @@ impl Context { /// Creates directories for RPC communication. pub fn create_socket_dir(&self) -> Result<()> { + // FIXME: GnuPG as packaged by MinGW fails to create socketdir because + // it follows upstream Unix logic, which expects Unix-like `/var/run` + // sockets to work. Additionally, GnuPG expects to work with and set + // correct POSIX permissions that MinGW does not even support/emulate, + // so this fails loudly. + // Instead, don't do anything and rely on on homedir being treated + // (correctly) as a fallback here. + #[cfg(windows)] + if self.cygwin { + return Ok(()); + } + Self::gpgconf(&self.homedir, &["--create-socketdir"], 1)?; Ok(()) } diff --git a/ipc/tests/gpg-agent.rs b/ipc/tests/gpg-agent.rs index 9fd4ed54..a2a6d286 100644 --- a/ipc/tests/gpg-agent.rs +++ b/ipc/tests/gpg-agent.rs @@ -69,7 +69,7 @@ fn gpg_import(ctx: &Context, what: &[u8]) { let mut gpg = Command::new("gpg") .stdin(Stdio::piped()) - .arg("--homedir").arg(ctx.directory("homedir").unwrap()) + .arg("--homedir").arg(ctx.homedir().unwrap()) .arg("--import") .spawn() .expect("failed to start gpg"); |