Skip to content

Commit

Permalink
sync.Mutex
Browse files Browse the repository at this point in the history
  • Loading branch information
abakum committed Nov 24, 2023
1 parent 179b607 commit c2b877a
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 6 deletions.
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

Expand All @@ -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`.<br>
The path to this pipe is exposed through the environment variable `SSH_AUTH_SOCK` like `\\.\pipe\somepath`<br>
The `ssh-agent` daemon of `OpenSSH for Windows` used `Named Pipe` `\\.\pipe\openssh-ssh-agent`<br>
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
Expand Down
8 changes: 8 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -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
)
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -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=
42 changes: 37 additions & 5 deletions pageant.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
// 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 (
"encoding/binary"
"fmt"
"io"
"reflect"
"sync"
"syscall"
"unsafe"

Expand Down Expand Up @@ -38,13 +40,18 @@ type Conn struct {
readOffset int
readLimit int
mapName string
sync.Mutex
}

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
}

Expand All @@ -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 {
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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(
Expand Down

0 comments on commit c2b877a

Please sign in to comment.