summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorIgor Matuszewski <igor@sequoia-pgp.org>2020-10-25 00:33:00 +0200
committerIgor Matuszewski <igor@sequoia-pgp.org>2020-11-01 19:13:33 +0100
commitc392cc06db1fa132a5b3ddd380667bddb4e08012 (patch)
treee837948c4927e7a893f4748a7b8f289c9ad43750
parenta0da43702413524e4a8a37def96fea0645e10cd8 (diff)
ipc: Support GnuPG on Cygwin/MinGW
-rw-r--r--ipc/src/gnupg.rs97
-rw-r--r--ipc/tests/gpg-agent.rs2
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");