summaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit.ssh.apache.agent
diff options
context:
space:
mode:
authorThomas Wolf <thomas.wolf@paranor.ch>2021-12-27 21:39:23 +0100
committerThomas Wolf <thomas.wolf@paranor.ch>2022-01-30 17:13:46 +0100
commite0281c5adb89f0b109abca57970c7b89df63ede4 (patch)
treeb6f90f08c5a4892f1a6ddd171d709e04dabc5c5c /org.eclipse.jgit.ssh.apache.agent
parent071084818cae26fd3f1075d4e6763218197c94d5 (diff)
downloadjgit-e0281c5adb89f0b109abca57970c7b89df63ede4.tar.gz
jgit-e0281c5adb89f0b109abca57970c7b89df63ede4.zip
sshd: Connector for the Win32-OpenSSH SSH agent
Win32-OpenSSH uses a named Windows pipe for communication. Implement a connector for this mechanism using JNA. Choose the appropriate connector based on the setting of the 'identityAgent' parameter. Bug: 577053 Change-Id: I205f07fb33654aa18ca5db92706e65544ce38641 Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
Diffstat (limited to 'org.eclipse.jgit.ssh.apache.agent')
-rw-r--r--org.eclipse.jgit.ssh.apache.agent/resources/org/eclipse/jgit/internal/transport/sshd/agent/connector/Texts.properties3
-rw-r--r--org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Factory.java25
-rw-r--r--org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/LibraryHolder.java4
-rw-r--r--org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Texts.java3
-rw-r--r--org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/WinPipeConnector.java216
5 files changed, 249 insertions, 2 deletions
diff --git a/org.eclipse.jgit.ssh.apache.agent/resources/org/eclipse/jgit/internal/transport/sshd/agent/connector/Texts.properties b/org.eclipse.jgit.ssh.apache.agent/resources/org/eclipse/jgit/internal/transport/sshd/agent/connector/Texts.properties
index 6fce083668..a3b4e91cad 100644
--- a/org.eclipse.jgit.ssh.apache.agent/resources/org/eclipse/jgit/internal/transport/sshd/agent/connector/Texts.properties
+++ b/org.eclipse.jgit.ssh.apache.agent/resources/org/eclipse/jgit/internal/transport/sshd/agent/connector/Texts.properties
@@ -2,9 +2,11 @@ errCloseMappedFile=Cannot close mapped file: {0} - {1}
errLastError=System message for error {0} could not be retrieved, got {1}
errReleaseSharedMemory=Cannot release shared memory: {0} - {1}
errUnknown=unknown error
+errUnknownIdentityAgent=IdentityAgent ''{0}'' unknown
logErrorLoadLibrary=Cannot load socket library; SSH agent support is switched off
msgCloseFailed=Cannot close SSH agent socket {0}
msgConnectFailed=Could not connect to SSH agent via socket ''{0}''
+msgConnectPipeFailed=Could not connect to SSH agent via pipe ''{0}''
msgNoMappedFile=Could not create file mapping: {0} - {1}
msgNoSharedMemory=Could not initialize shared memory: {0} - {1}
msgPageantUnavailable=Could not connect to Pageant
@@ -15,3 +17,4 @@ msgSharedMemoryFailed=Could not set up shared memory for communicating with Page
msgShortRead=Short read from SSH agent, expected {0} bytes, got {1} bytes; last read() returned {2}
pageant=Pageant
unixDefaultAgent=ssh-agent
+winOpenSsh=Win32 OpenSSH
diff --git a/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Factory.java b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Factory.java
index d7409b0c3c..1cee1be13e 100644
--- a/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Factory.java
+++ b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Factory.java
@@ -11,11 +11,15 @@ package org.eclipse.jgit.internal.transport.sshd.agent.connector;
import java.io.File;
import java.io.IOException;
+import java.text.MessageFormat;
import java.util.Collection;
import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
import org.eclipse.jgit.transport.sshd.agent.Connector;
import org.eclipse.jgit.transport.sshd.agent.ConnectorFactory;
+import org.eclipse.jgit.util.StringUtils;
import org.eclipse.jgit.util.SystemReader;
/**
@@ -29,7 +33,20 @@ public class Factory implements ConnectorFactory {
public Connector create(String identityAgent, File homeDir)
throws IOException {
if (SystemReader.getInstance().isWindows()) {
- return new PageantConnector();
+ if (StringUtils.isEmptyOrNull(identityAgent)) {
+ // Default.
+ return new PageantConnector();
+ }
+ String winPath = identityAgent.replace('/', '\\');
+ if (PageantConnector.DESCRIPTOR.getIdentityAgent()
+ .equalsIgnoreCase(winPath)) {
+ return new PageantConnector();
+ }
+ if (winPath.toLowerCase(Locale.ROOT).startsWith("\\\\.\\pipe\\")) { //$NON-NLS-1$
+ return new WinPipeConnector(winPath);
+ }
+ throw new IOException(MessageFormat.format(
+ Texts.get().errUnknownIdentityAgent, identityAgent));
}
return new UnixDomainSocketConnector(identityAgent);
}
@@ -55,7 +72,11 @@ public class Factory implements ConnectorFactory {
*/
@Override
public Collection<ConnectorDescriptor> getSupportedConnectors() {
- return Collections.singleton(getDefaultConnector());
+ if (SystemReader.getInstance().isWindows()) {
+ return List.of(PageantConnector.DESCRIPTOR,
+ WinPipeConnector.DESCRIPTOR);
+ }
+ return Collections.singleton(UnixDomainSocketConnector.DESCRIPTOR);
}
@Override
diff --git a/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/LibraryHolder.java b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/LibraryHolder.java
index b09b55f817..0a592d0500 100644
--- a/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/LibraryHolder.java
+++ b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/LibraryHolder.java
@@ -53,6 +53,10 @@ class LibraryHolder {
kernel = Kernel32.INSTANCE;
}
+ String systemError() {
+ return systemError("[{0}] - {1}"); //$NON-NLS-1$
+ }
+
String systemError(String pattern) {
int lastError = kernel.GetLastError();
String msg;
diff --git a/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Texts.java b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Texts.java
index fb45b30dd2..f387c76ad8 100644
--- a/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Texts.java
+++ b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/Texts.java
@@ -31,9 +31,11 @@ public final class Texts extends TranslationBundle {
/***/ public String errLastError;
/***/ public String errReleaseSharedMemory;
/***/ public String errUnknown;
+ /***/ public String errUnknownIdentityAgent;
/***/ public String logErrorLoadLibrary;
/***/ public String msgCloseFailed;
/***/ public String msgConnectFailed;
+ /***/ public String msgConnectPipeFailed;
/***/ public String msgNoMappedFile;
/***/ public String msgNoSharedMemory;
/***/ public String msgPageantUnavailable;
@@ -44,5 +46,6 @@ public final class Texts extends TranslationBundle {
/***/ public String msgShortRead;
/***/ public String pageant;
/***/ public String unixDefaultAgent;
+ /***/ public String winOpenSsh;
}
diff --git a/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/WinPipeConnector.java b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/WinPipeConnector.java
new file mode 100644
index 0000000000..7bad90f24f
--- /dev/null
+++ b/org.eclipse.jgit.ssh.apache.agent/src/org/eclipse/jgit/internal/transport/sshd/agent/connector/WinPipeConnector.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2021, Thomas Wolf <thomas.wolf@paranor.ch> and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.internal.transport.sshd.agent.connector;
+
+import java.io.IOException;
+import java.text.MessageFormat;
+import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.sshd.common.SshException;
+import org.eclipse.jgit.transport.sshd.agent.AbstractConnector;
+import org.eclipse.jgit.transport.sshd.agent.ConnectorFactory.ConnectorDescriptor;
+import org.eclipse.jgit.util.StringUtils;
+
+import com.sun.jna.LastErrorException;
+import com.sun.jna.platform.win32.WinBase;
+import com.sun.jna.platform.win32.WinError;
+import com.sun.jna.platform.win32.WinNT;
+import com.sun.jna.platform.win32.WinNT.HANDLE;
+import com.sun.jna.ptr.IntByReference;
+
+/**
+ * A connector based on JNA using Windows' named pipes to communicate with an
+ * ssh agent. This is used by Microsoft's Win32-OpenSSH port.
+ */
+public class WinPipeConnector extends AbstractConnector {
+
+ // Pipe names are, like other file names, case-insensitive on Windows.
+ private static final String CANONICAL_PIPE_NAME = "\\\\.\\pipe\\openssh-ssh-agent"; //$NON-NLS-1$
+
+ /**
+ * {@link ConnectorDescriptor} for the {@link PageantConnector}.
+ */
+ public static final ConnectorDescriptor DESCRIPTOR = new ConnectorDescriptor() {
+
+ @Override
+ public String getIdentityAgent() {
+ return CANONICAL_PIPE_NAME;
+ }
+
+ @Override
+ public String getDisplayName() {
+ return Texts.get().winOpenSsh;
+ }
+ };
+
+ private static final int FILE_SHARE_NONE = 0;
+
+ private static final int FILE_ATTRIBUTE_NONE = 0;
+
+ private final String pipeName;
+
+ private final AtomicBoolean connected = new AtomicBoolean();
+
+ // It's a byte pipe, so the normal Windows file mechanisms can be used.
+ // Would one of the standard Java File I/O abstractions work?
+ private volatile HANDLE fileHandle;
+
+ /**
+ * Creates a {@link WinPipeConnector} for the given named pipe.
+ *
+ * @param pipeName
+ * to connect to
+ */
+ public WinPipeConnector(String pipeName) {
+ this.pipeName = pipeName.replace('/', '\\');
+ }
+
+ @Override
+ public boolean connect() throws IOException {
+ if (StringUtils.isEmptyOrNull(pipeName)) {
+ return false;
+ }
+ HANDLE file = fileHandle;
+ synchronized (this) {
+ if (connected.get()) {
+ return true;
+ }
+ LibraryHolder libs = LibraryHolder.getLibrary();
+ if (libs == null) {
+ return false;
+ }
+ file = libs.kernel.CreateFile(pipeName,
+ WinNT.GENERIC_READ | WinNT.GENERIC_WRITE, FILE_SHARE_NONE,
+ null, WinNT.OPEN_EXISTING, FILE_ATTRIBUTE_NONE, null);
+ if (file == null || file == WinBase.INVALID_HANDLE_VALUE) {
+ int errorCode = libs.kernel.GetLastError();
+ if (errorCode == WinError.ERROR_FILE_NOT_FOUND
+ && CANONICAL_PIPE_NAME.equalsIgnoreCase(pipeName)) {
+ // OpenSSH agent not running. Don't throw.
+ return false;
+ }
+ LastErrorException cause = new LastErrorException(
+ libs.systemError());
+ throw new IOException(MessageFormat
+ .format(Texts.get().msgConnectPipeFailed, pipeName),
+ cause);
+ }
+ connected.set(true);
+ }
+ fileHandle = file;
+ return connected.get();
+ }
+
+ @Override
+ public synchronized void close() throws IOException {
+ HANDLE file = fileHandle;
+ if (connected.getAndSet(false) && fileHandle != null) {
+ fileHandle = null;
+ LibraryHolder libs = LibraryHolder.getLibrary();
+ boolean success = libs.kernel.CloseHandle(file);
+ if (!success) {
+ LastErrorException cause = new LastErrorException(
+ libs.systemError());
+ throw new IOException(MessageFormat
+ .format(Texts.get().msgCloseFailed, pipeName), cause);
+ }
+ }
+ }
+
+ @Override
+ public byte[] rpc(byte command, byte[] message) throws IOException {
+ prepareMessage(command, message);
+ HANDLE file = fileHandle;
+ if (!connected.get() || file == null) {
+ // No translation, internal error
+ throw new IllegalStateException("Not connected to SSH agent"); //$NON-NLS-1$
+ }
+ LibraryHolder libs = LibraryHolder.getLibrary();
+ writeFully(libs, file, message);
+ // Now receive the reply
+ byte[] lengthBuf = new byte[4];
+ readFully(libs, file, lengthBuf);
+ int length = toLength(command, lengthBuf);
+ byte[] payload = new byte[length];
+ readFully(libs, file, payload);
+ return payload;
+ }
+
+ private void writeFully(LibraryHolder libs, HANDLE file, byte[] message)
+ throws IOException {
+ byte[] buf = message;
+ int toWrite = buf.length;
+ try {
+ while (toWrite > 0) {
+ IntByReference written = new IntByReference();
+ boolean success = libs.kernel.WriteFile(file, buf, buf.length,
+ written, null);
+ if (!success) {
+ throw new LastErrorException(libs.systemError());
+ }
+ int actuallyWritten = written.getValue();
+ toWrite -= actuallyWritten;
+ if (actuallyWritten > 0 && toWrite > 0) {
+ buf = Arrays.copyOfRange(buf, actuallyWritten, buf.length);
+ }
+ }
+ } catch (LastErrorException e) {
+ throw new IOException(MessageFormat.format(
+ Texts.get().msgSendFailed, Integer.toString(message.length),
+ Integer.toString(toWrite)), e);
+ }
+ }
+
+ private void readFully(LibraryHolder libs, HANDLE file, byte[] data)
+ throws IOException {
+ int n = 0;
+ int offset = 0;
+ while (offset < data.length && (n = read(libs, file, data, offset,
+ data.length - offset)) > 0) {
+ offset += n;
+ }
+ if (offset < data.length) {
+ throw new SshException(MessageFormat.format(
+ Texts.get().msgShortRead, Integer.toString(data.length),
+ Integer.toString(offset), Integer.toString(n)));
+ }
+ }
+
+ private int read(LibraryHolder libs, HANDLE file, byte[] buffer, int offset,
+ int length) throws IOException {
+ try {
+ int toRead = length;
+ IntByReference read = new IntByReference();
+ if (offset == 0) {
+ boolean success = libs.kernel.ReadFile(file, buffer, toRead,
+ read, null);
+ if (!success) {
+ throw new LastErrorException(libs.systemError());
+ }
+ return read.getValue();
+ }
+ byte[] data = new byte[length];
+ boolean success = libs.kernel.ReadFile(file, buffer, toRead, read,
+ null);
+ if (!success) {
+ throw new LastErrorException(libs.systemError());
+ }
+ int actuallyRead = read.getValue();
+ if (actuallyRead > 0) {
+ System.arraycopy(data, 0, buffer, offset, actuallyRead);
+ }
+ return actuallyRead;
+ } catch (LastErrorException e) {
+ throw new IOException(MessageFormat.format(
+ Texts.get().msgReadFailed, Integer.toString(length)), e);
+ }
+ }
+}