From c2b877a058f4ed8efb0c42f8494ef695c83cc7cf Mon Sep 17 00:00:00 2001 From: abakum Date: Fri, 24 Nov 2023 16:27:19 +0300 Subject: [PATCH] sync.Mutex --- README.md | 19 ++++++++++++++++++- go.mod | 8 ++++++++ go.sum | 6 ++++++ pageant.go | 42 +++++++++++++++++++++++++++++++++++++----- 4 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 go.mod create mode 100644 go.sum diff --git a/README.md b/README.md index 8cf1e8c..ec28a1a 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ func main() { ## Unix/Linux Alternatives The `ssh-agent` command implements the same [SSH agent protocol][ssh-agent] -as Pageant, but over a Unix domain socket instead of shared memory. +as Pageant, but over a `Unix domain socket` instead of shared memory. The path to this socket is exposed through the environment variable `SSH_AUTH_SOCK`. @@ -60,6 +60,23 @@ Replace the connection to Pageant with one to the socket: agentConn, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK")) ``` +## OpenSSH for Windows Alternatives + +The `ssh-add`, `ssh` commands of `OpenSSH for Windows` implements the same [SSH agent protocol][ssh-agent] +as Unix/Linux, but over a `Named Pipe` instead of `Unix domain socket`.
+The path to this pipe is exposed through the environment variable `SSH_AUTH_SOCK` like `\\.\pipe\somepath`
+The `ssh-agent` daemon of `OpenSSH for Windows` used `Named Pipe` `\\.\pipe\openssh-ssh-agent`
+The `sshd` daemon of `OpenSSH for Windows` used `Unix domain socket` like `/tmp/somepath` for some versions of Windows it works + +Replace the connection to Pageant with one to the socket: +```golang + // instead of this: + agentConn, err := pageant.NewConn() + // do this: + agentConn, err := winio.DialPipe(os.Getenv("SSH_AUTH_SOCK"), nil) +``` + + [ssh-agent]: https://tools.ietf.org/html/draft-miller-ssh-agent-02 ## Testing diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..6ab7d6b --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module github.com/abakum/pageant + +go 1.21.3 + +require ( + golang.org/x/crypto v0.15.0 + golang.org/x/sys v0.14.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..161964f --- /dev/null +++ b/go.sum @@ -0,0 +1,6 @@ +golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA= +golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8= +golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= diff --git a/pageant.go b/pageant.go index ac96af0..5f11cdb 100644 --- a/pageant.go +++ b/pageant.go @@ -1,6 +1,7 @@ // Package pageant provides native Go support for using PuTTY Pageant as an // SSH agent with the golang.org/x/crypto/ssh/agent package. // Based loosely on the Java JNA package jsch-agent-proxy-pageant. +// Based on https://github.com/kbolino/pageant of Kristian Bolino package pageant import ( @@ -8,6 +9,7 @@ import ( "fmt" "io" "reflect" + "sync" "syscall" "unsafe" @@ -38,6 +40,7 @@ type Conn struct { readOffset int readLimit int mapName string + sync.Mutex } var _ io.ReadWriteCloser = &Conn{} @@ -45,6 +48,10 @@ var _ io.ReadWriteCloser = &Conn{} // NewConn creates a new connection to Pageant. // Ensure Close gets called on the returned Conn when it is no longer needed. func NewConn() (*Conn, error) { + _, err := PageantWindow() + if err != nil { + return nil, err + } return &Conn{}, nil } @@ -53,6 +60,10 @@ func (c *Conn) Close() error { if c.sharedMem == 0 { return nil } + + c.Lock() + defer c.Unlock() + errUnmap := windows.UnmapViewOfFile(c.sharedMem) errClose := windows.CloseHandle(c.sharedFile) if errUnmap != nil { @@ -73,6 +84,10 @@ func (c *Conn) Read(p []byte) (n int, err error) { } else if c.readOffset == c.readLimit { return 0, io.EOF } + + c.Lock() + defer c.Unlock() + bytesToRead := minInt(len(p), c.readLimit-c.readOffset) src := toSlice(c.sharedMem+uintptr(c.readOffset), bytesToRead) copy(p, src) @@ -92,9 +107,14 @@ func (c *Conn) Write(p []byte) (n int, err error) { return 0, fmt.Errorf("failed to close previous connection: %s", err) } } + if err := c.establishConn(); err != nil { return 0, fmt.Errorf("failed to connect to Pageant: %s", err) } + + c.Lock() + defer c.Unlock() + dst := toSlice(c.sharedMem, len(p)) copy(dst, p) data := make([]byte, len(c.mapName)+1) @@ -116,19 +136,31 @@ func (c *Conn) Write(p []byte) (n int, err error) { return len(p), nil } -// establishConn creates a new connection to Pageant. -func (c *Conn) establishConn() error { - window, _, err := findWindow.Call( +// used in establishConn and NewConn +func PageantWindow() (window uintptr, err error) { + window, _, err = findWindow.Call( uintptr(unsafe.Pointer(pageantWindowName)), uintptr(unsafe.Pointer(pageantWindowName)), ) if window == 0 { if err != nil && err != noError { - return fmt.Errorf("cannot find Pageant window: %s", err) + err = fmt.Errorf("cannot find Pageant window: %s", err) } else { - return fmt.Errorf("cannot find Pageant window, ensure Pageant is running") + err = fmt.Errorf("cannot find Pageant window, ensure Pageant is running") } + } else { + err = nil } + return +} + +// establishConn creates a new connection to Pageant. +func (c *Conn) establishConn() error { + window, err := PageantWindow() + if err != nil { + return err + } + mapName := fmt.Sprintf("PageantRequest%08x", windows.GetCurrentThreadId()) mapNameUTF16 := utf16Ptr(mapName) sharedFile, err := windows.CreateFileMapping(