├── .gitignore ├── LICENSE ├── Readme.md ├── assets └── icon.ico ├── azure-pipelines.yml ├── build.ps1 ├── go.mod ├── go.sum └── main.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Visual Studio Code 2 | .vscode/* 3 | 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Generated file 18 | assets.go 19 | 20 | # Build artifacts 21 | build/* 22 | release.zip 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Ben Pye and contributors 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 10 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # wsl-ssh-pageant 2 | 3 | [![Build Status](https://benpye.visualstudio.com/benpye/_apis/build/status/benpye.wsl-ssh-pageant?branchName=golang)](https://benpye.visualstudio.com/benpye/_build/latest?definitionId=1&branchName=golang) 4 | 5 | ## Why 6 | I use a Yubikey to store a GPG key pair and I like to use this key pair as my SSH key too. GPG on Windows exposes a Pageant style SSH agent and I wanted a way to use this key within WSL. I have rewritten this in Go as it means the release is a single simple binary, and I like Go. 7 | 8 | ## How to use with WSL 9 | 10 | 1. On the Windows side start Pageant (or compatible agent such as gpg4win). 11 | 12 | 2. Run `wsl-ssh-pageant.exe --wsl C:\wsl-ssh-pageant\ssh-agent.sock` (or any other path, max ~100 characters) 13 | 14 | 3. In WSL export the `SSH_AUTH_SOCK` environment variable to point at the socket, for example, if you have `ssh-agent.sock` in `C:\wsl-ssh-pageant` 15 | ``` 16 | $ export SSH_AUTH_SOCK=/mnt/c/wsl-ssh-pageant/ssh-agent.sock 17 | ``` 18 | 19 | 4. The SSH keys from Pageant should now be usable by `ssh` 20 | 21 | ## How to use with Windows 10 native OpenSSH client 22 | 23 | 1. On the Windows side start Pageant (or compatible agent such as gpg4win). 24 | 25 | 2. Run `wsl-ssh-pageant.exe --winssh ssh-pageant` (or any other name) 26 | 27 | 3. In `cmd` export the `SSH_AUTH_SOCK` environment variable or define it in your Environment Variables on Windows. Use the name you gave the pipe, for example: 28 | 29 | ``` 30 | $ set SSH_AUTH_SOCK=\\.\pipe\ssh-pageant 31 | ``` 32 | 33 | 4. The SSH keys from Pageant should now be usable by the native Windows SSH client, try using `ssh` in `cmd.exe` 34 | 35 | ## Systray Integration 36 | 37 | To add an icon to the systray run `wsl-ssh-pageant.exe --systray --winssh ssh-pageant` (or using `--wsl`). 38 | 39 | ## Note 40 | 41 | You can use both `--winssh` and `--wsl` parameters at the same time with the same process to proxy for both 42 | 43 | # Frequently asked questions 44 | 45 | ## How do I download it? 46 | Grab the latest release on the [releases page](https://github.com/benpye/wsl-ssh-pageant/releases). 47 | 48 | ## How do I build this? 49 | For WSL support you will need Go 1.12 or later,. Go 1.12 added support for `AF_UNIX` sockets on Windows. 50 | 51 | To create the assets.go run: 52 | ``` 53 | go generate 54 | ``` 55 | 56 | To create a build without a console window: 57 | ``` 58 | go build -ldflags -H=windowsgui 59 | ``` 60 | 61 | ## What version of Windows do I need? 62 | You need Windows 10 1803 or later for WSL support as it is the first version supporting `AF_UNIX` sockets. You can still use this with the native [Windows SSH client](https://github.com/PowerShell/Win32-OpenSSH/releases) on earlier builds. 63 | 64 | ## The -gui.exe binary doesn't have a GUI? (immediately closes) 65 | The difference between the gui.exe binary and the regular binaries is the subsystem as set in the PE header. The gui.exe binary is set with the Win32 subsystem so that it doesn't spawn a command line, allowing it to be launched on startup. The regular binary has the console subsystem so it does launch a command line if double clicked, and will block the command line as expected. Note: You may launch either binary with the `-systray` flag to have a systray icon whilst the tool is running, this only provides a way to quit the application. 66 | 67 | ## You didn't answer my question! 68 | Please open an issue, I do try and keep on top of them, promise. 69 | 70 | # Credit 71 | 72 | * Thanks to [John Starks](https://github.com/jstarks/) for [npiperelay](https://github.com/jstarks/npiperelay/) for an example of a more secure way to create a stream between WSL and Linux before `AF_UNIX` sockets were available. 73 | * Thanks for [Mark Dietzer](https://github.com/Doridian) for several contributions to the old .NET implementation. 74 | -------------------------------------------------------------------------------- /assets/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benpye/wsl-ssh-pageant/6ebd3561ce8b034b39ef93d5558c62cf44c541da/assets/icon.ico -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | trigger: 2 | - golang 3 | 4 | pool: 5 | vmImage: 'vs2017-win2016' 6 | 7 | variables: 8 | GOBIN: '$(GOPATH)\bin' # Go binaries path 9 | GOPATH: '$(system.defaultWorkingDirectory)\gopath' # Go workspace path 10 | 11 | steps: 12 | - task: PowerShell@2 13 | inputs: 14 | filePath: '.\build.ps1' 15 | arguments: '-Release' 16 | - task: PublishPipelineArtifact@0 17 | inputs: 18 | artifactName: 'build' 19 | targetPath: '.\build' 20 | - task: GitHubRelease@0 21 | displayName: 'GitHub release (create)' 22 | condition: eq(variables['Build.SourceBranch'], 'refs/heads/golang') 23 | inputs: 24 | repositoryName: '$(Build.Repository.Name)' 25 | action: 'create' 26 | target: '$(Build.SourceVersion)' 27 | tagSource: 'manual' 28 | tag: '$(Build.BuildNumber)' 29 | assets: 'build\*' 30 | isPreRelease: true 31 | gitHubConnection: 'github connection 1' 32 | -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [string] 3 | $BuildPath = ".\build", 4 | 5 | [string[]] 6 | $Architectures = @("amd64","386"), 7 | 8 | [switch] 9 | $Release = $false, 10 | 11 | [string] 12 | $ReleasePath = ".\release.zip" 13 | ) 14 | 15 | # Cleanup 16 | Remove-item -LiteralPath .\assets.go -ErrorAction SilentlyContinue 17 | Remove-Item -LiteralPath $BuildPath -Force -Recurse -ErrorAction SilentlyContinue 18 | 19 | # Build output directory 20 | $outDir = New-Item -ItemType Directory -Path $BuildPath 21 | 22 | $oldGOOS = $env:GOOS 23 | $oldGOARCH = $env:GOARCH 24 | 25 | $env:GOOS="windows" 26 | $env:GOARCH=$null 27 | 28 | $returnValue = 0 29 | 30 | # Generate assets.go 31 | go generate 32 | if ($LastExitCode -ne 0) { $returnValue = $LastExitCode } 33 | 34 | # Build for each architecture 35 | Foreach ($arch in $Architectures) 36 | { 37 | $env:GOARCH=$arch 38 | go build -o $outDir\wsl-ssh-pageant-$arch.exe 39 | if ($LastExitCode -ne 0) { $returnValue = $LastExitCode } 40 | go build -ldflags -H=windowsgui -o $outDir\wsl-ssh-pageant-$arch-gui.exe 41 | if ($LastExitCode -ne 0) { $returnValue = $LastExitCode } 42 | } 43 | 44 | # Build release package 45 | if ($Release) 46 | { 47 | Copy-Item Readme.md $outDir 48 | Copy-Item LICENSE $outDir 49 | 50 | Remove-Item -LiteralPath $ReleasePath -ErrorAction SilentlyContinue 51 | Compress-Archive -Path $outDir\* -DestinationPath $ReleasePath 52 | } 53 | 54 | # Restore env vars 55 | $env:GOOS = $oldGOOS 56 | $env:GOARCH = $oldGOARCH 57 | 58 | exit $returnValue 59 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/benpye/wsl-ssh-pageant 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/Microsoft/go-winio v0.4.11 7 | github.com/apenwarr/fixconsole v0.0.0-20190407065125-35b2e7d921eb 8 | github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 // indirect 9 | github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 // indirect 10 | github.com/getlantern/golog v0.0.0-20170508214112-cca714f7feb5 // indirect 11 | github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 // indirect 12 | github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 // indirect 13 | github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f // indirect 14 | github.com/getlantern/systray v0.0.0-20190131073753-26d5b920200d 15 | github.com/go-bindata/go-bindata v3.1.1+incompatible // indirect 16 | github.com/go-stack/stack v1.8.0 // indirect 17 | github.com/lxn/win v0.0.0-20181015143721-a7f87360b10e 18 | github.com/oxtoacart/bpool v0.0.0-20190227141107-8c4636f812cc // indirect 19 | github.com/stretchr/testify v1.3.0 // indirect 20 | golang.org/x/sys v0.0.0-20200317113312-5766fd39f98d 21 | ) 22 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Microsoft/go-winio v0.4.11 h1:zoIOcVf0xPN1tnMVbTtEdI+P8OofVk3NObnwOQ6nK2Q= 2 | github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= 3 | github.com/apenwarr/fixconsole v0.0.0-20190407065125-35b2e7d921eb h1:qvbjnzHNDCZ5C2wDOqAhUihbAKtpCDnD2WW5wk5Y/gY= 4 | github.com/apenwarr/fixconsole v0.0.0-20190407065125-35b2e7d921eb/go.mod h1:JYWahgHer+Z2xbsgHPtaDYVWzeHDminu+YIBWkxpCAY= 5 | github.com/apenwarr/w32 v0.0.0-20190407065021-aa00fece76ab h1:CMGzRRCjnD50RjUFSArBLuCxiDvdp7b8YPAcikBEQ+k= 6 | github.com/apenwarr/w32 v0.0.0-20190407065021-aa00fece76ab/go.mod h1:nfFtvHn2Hgs9G1u0/J6LHQv//EksNC+7G8vXmd1VTJ8= 7 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 h1:NRUJuo3v3WGC/g5YiyF790gut6oQr5f3FBI88Wv0dx4= 10 | github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY= 11 | github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 h1:6uJ+sZ/e03gkbqZ0kUG6mfKoqDb4XMAzMIwlajq19So= 12 | github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7/go.mod h1:l+xpFBrCtDLpK9qNjxs+cHU6+BAdlBaxHqikB6Lku3A= 13 | github.com/getlantern/golog v0.0.0-20170508214112-cca714f7feb5 h1:Okd7vkn9CfIgDBj1ST/vtBTCfD/kxIhYD412K+FRKPc= 14 | github.com/getlantern/golog v0.0.0-20170508214112-cca714f7feb5/go.mod h1:Vwx1Cg64gCdIalad44uvQsKZw6LsVczIKZrUBStEjVw= 15 | github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 h1:micT5vkcr9tOVk1FiH8SWKID8ultN44Z+yzd2y/Vyb0= 16 | github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7/go.mod h1:dD3CgOrwlzca8ed61CsZouQS5h5jIzkK9ZWrTcf0s+o= 17 | github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 h1:XYzSdCbkzOC0FDNrgJqGRo8PCMFOBFL9py72DRs7bmc= 18 | github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55/go.mod h1:6mmzY2kW1TOOrVy+r41Za2MxXM+hhqTtY3oBKd2AgFA= 19 | github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f h1:wrYrQttPS8FHIRSlsrcuKazukx/xqO/PpLZzZXsF+EA= 20 | github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA= 21 | github.com/getlantern/systray v0.0.0-20190131073753-26d5b920200d h1:4P2eDMAoQcQoWIIKCNIkuVbQb+paRmpMxVXVfbs7B4U= 22 | github.com/getlantern/systray v0.0.0-20190131073753-26d5b920200d/go.mod h1:7Splj4WBQSps8jODnMgrIV6goKL0N1HR+mhCAEVWlA0= 23 | github.com/go-bindata/go-bindata v3.1.1+incompatible h1:tR4f0e4VTO7LK6B2YWyAoVEzG9ByG1wrXB4TL9+jiYg= 24 | github.com/go-bindata/go-bindata v3.1.1+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo= 25 | github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= 26 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 27 | github.com/lxn/win v0.0.0-20181015143721-a7f87360b10e h1:dz4TzIsrPe4XtUyhLkOLdCS8UkVwJKQu4WY8VcIwo3I= 28 | github.com/lxn/win v0.0.0-20181015143721-a7f87360b10e/go.mod h1:jACzEp9RV7NhfPJQkiCNTteU4nkZZVlvkNpYtVOZPfE= 29 | github.com/oxtoacart/bpool v0.0.0-20190227141107-8c4636f812cc h1:uhnyuvDwdKbjemAXHKsiEZOPagHim2nRjMcazH1g26A= 30 | github.com/oxtoacart/bpool v0.0.0-20190227141107-8c4636f812cc/go.mod h1:L3UMQOThbttwfYRNFOWLLVXMhk5Lkio4GGOtw5UrxS0= 31 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 32 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 33 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 34 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 35 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 36 | golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67 h1:1Fzlr8kkDLQwqMP8GxrhptBLqZG/EDpiATneiZHY998= 37 | golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 38 | golang.org/x/sys v0.0.0-20200317113312-5766fd39f98d h1:62ap6LNOjDU6uGmKXHJbSfciMoV+FeI1sRXx/pLDL44= 39 | golang.org/x/sys v0.0.0-20200317113312-5766fd39f98d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 40 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //go:generate go run github.com/go-bindata/go-bindata/go-bindata -pkg $GOPACKAGE -o assets.go assets/ 4 | 5 | import ( 6 | "bufio" 7 | "encoding/binary" 8 | "errors" 9 | "flag" 10 | "fmt" 11 | "io" 12 | "log" 13 | "net" 14 | "os" 15 | "os/signal" 16 | "os/user" 17 | "reflect" 18 | "sync" 19 | "syscall" 20 | "unsafe" 21 | 22 | "github.com/Microsoft/go-winio" 23 | "github.com/apenwarr/fixconsole" 24 | "github.com/getlantern/systray" 25 | "github.com/lxn/win" 26 | "golang.org/x/sys/windows" 27 | ) 28 | 29 | var ( 30 | unixSocket = flag.String("wsl", "", "Path to Unix socket for passthrough to WSL") 31 | namedPipe = flag.String("winssh", "", "Named pipe for use with Win32 OpenSSH") 32 | verbose = flag.Bool("verbose", false, "Enable verbose logging") 33 | systrayFlag = flag.Bool("systray", false, "Enable systray integration") 34 | force = flag.Bool("force", false, "Force socket usage (unlink existing socket)") 35 | ) 36 | 37 | const ( 38 | // Windows constats 39 | invalidHandleValue = ^windows.Handle(0) 40 | pageReadWrite = 0x4 41 | fileMapWrite = 0x2 42 | 43 | // ssh-agent/Pageant constants 44 | agentMaxMessageLength = 8192 45 | agentCopyDataID = 0x804e50ba 46 | ) 47 | 48 | // copyDataStruct is used to pass data in the WM_COPYDATA message. 49 | // We directly pass a pointer to our copyDataStruct type, we need to be 50 | // careful that it matches the Windows type exactly 51 | type copyDataStruct struct { 52 | dwData uintptr 53 | cbData uint32 54 | lpData uintptr 55 | } 56 | 57 | type SecurityAttributes struct { 58 | Length uint32 59 | SecurityDescriptor uintptr 60 | InheritHandle uint32 61 | } 62 | 63 | var queryPageantMutex sync.Mutex 64 | 65 | func makeInheritSaWithSid() *windows.SecurityAttributes { 66 | var sa windows.SecurityAttributes 67 | 68 | u, err := user.Current() 69 | 70 | if err == nil { 71 | sd, err := windows.SecurityDescriptorFromString("O:" + u.Uid) 72 | if err == nil { 73 | sa.SecurityDescriptor = sd 74 | } 75 | } 76 | 77 | sa.Length = uint32(unsafe.Sizeof(sa)) 78 | 79 | sa.InheritHandle = 1 80 | 81 | return &sa 82 | 83 | } 84 | 85 | func queryPageant(buf []byte) (result []byte, err error) { 86 | if len(buf) > agentMaxMessageLength { 87 | err = errors.New("Message too long") 88 | return 89 | } 90 | 91 | hwnd := win.FindWindow(syscall.StringToUTF16Ptr("Pageant"), syscall.StringToUTF16Ptr("Pageant")) 92 | 93 | if hwnd == 0 { 94 | err = errors.New("Could not find Pageant window") 95 | return 96 | } 97 | 98 | // Typically you'd add thread ID here but thread ID isn't useful in Go 99 | // We would need goroutine ID but Go hides this and provides no good way of 100 | // accessing it, instead we serialise calls to queryPageant and treat it 101 | // as not being goroutine safe 102 | mapName := fmt.Sprintf("WSLPageantRequest") 103 | queryPageantMutex.Lock() 104 | 105 | var sa = makeInheritSaWithSid() 106 | 107 | fileMap, err := windows.CreateFileMapping(invalidHandleValue, sa, pageReadWrite, 0, agentMaxMessageLength, syscall.StringToUTF16Ptr(mapName)) 108 | if err != nil { 109 | queryPageantMutex.Unlock() 110 | return 111 | } 112 | defer func() { 113 | windows.CloseHandle(fileMap) 114 | queryPageantMutex.Unlock() 115 | }() 116 | 117 | sharedMemory, err := windows.MapViewOfFile(fileMap, fileMapWrite, 0, 0, 0) 118 | if err != nil { 119 | return 120 | } 121 | defer windows.UnmapViewOfFile(sharedMemory) 122 | 123 | sharedMemoryArray := (*[agentMaxMessageLength]byte)(unsafe.Pointer(sharedMemory)) 124 | copy(sharedMemoryArray[:], buf) 125 | 126 | mapNameWithNul := mapName + "\000" 127 | 128 | // We use our knowledge of Go strings to get the length and pointer to the 129 | // data and the length directly 130 | cds := copyDataStruct{ 131 | dwData: agentCopyDataID, 132 | cbData: uint32(((*reflect.StringHeader)(unsafe.Pointer(&mapNameWithNul))).Len), 133 | lpData: ((*reflect.StringHeader)(unsafe.Pointer(&mapNameWithNul))).Data, 134 | } 135 | 136 | ret := win.SendMessage(hwnd, win.WM_COPYDATA, 0, uintptr(unsafe.Pointer(&cds))) 137 | if ret == 0 { 138 | err = errors.New("WM_COPYDATA failed") 139 | return 140 | } 141 | 142 | len := binary.BigEndian.Uint32(sharedMemoryArray[:4]) 143 | len += 4 144 | 145 | if len > agentMaxMessageLength { 146 | err = errors.New("Return message too long") 147 | return 148 | } 149 | 150 | result = make([]byte, len) 151 | copy(result, sharedMemoryArray[:len]) 152 | 153 | return 154 | } 155 | 156 | var failureMessage = [...]byte{0, 0, 0, 1, 5} 157 | 158 | func handleConnection(conn net.Conn) { 159 | defer conn.Close() 160 | 161 | reader := bufio.NewReader(conn) 162 | 163 | for { 164 | lenBuf := make([]byte, 4) 165 | _, err := io.ReadFull(reader, lenBuf) 166 | if err != nil { 167 | if *verbose { 168 | log.Printf("io.ReadFull error '%s'", err) 169 | } 170 | return 171 | } 172 | 173 | len := binary.BigEndian.Uint32(lenBuf) 174 | buf := make([]byte, len) 175 | _, err = io.ReadFull(reader, buf) 176 | if err != nil { 177 | if *verbose { 178 | log.Printf("io.ReadFull error '%s'", err) 179 | } 180 | return 181 | } 182 | 183 | result, err := queryPageant(append(lenBuf, buf...)) 184 | if err != nil { 185 | // If for some reason talking to Pageant fails we fall back to 186 | // sending an agent error to the client 187 | if *verbose { 188 | log.Printf("Pageant query error '%s'", err) 189 | } 190 | result = failureMessage[:] 191 | } 192 | 193 | _, err = conn.Write(result) 194 | if err != nil { 195 | if *verbose { 196 | log.Printf("net.Conn.Write error '%s'", err) 197 | } 198 | return 199 | } 200 | } 201 | } 202 | 203 | func listenLoop(ln net.Listener) { 204 | defer ln.Close() 205 | 206 | for { 207 | conn, err := ln.Accept() 208 | if err != nil { 209 | log.Printf("net.Listener.Accept error '%s'", err) 210 | return 211 | } 212 | 213 | if *verbose { 214 | log.Printf("New connection: %v\n", conn) 215 | } 216 | 217 | go handleConnection(conn) 218 | } 219 | } 220 | 221 | func main() { 222 | fixconsole.FixConsoleIfNeeded() 223 | flag.Parse() 224 | 225 | var unix, pipe net.Listener 226 | var err error 227 | 228 | done := make(chan bool, 1) 229 | 230 | sigs := make(chan os.Signal, 1) 231 | signal.Notify(sigs, os.Interrupt, syscall.SIGTERM) 232 | go func() { 233 | sig := <-sigs 234 | switch sig { 235 | case os.Interrupt: 236 | log.Printf("Caught signal") 237 | done <- true 238 | } 239 | }() 240 | 241 | if *unixSocket != "" { 242 | _, err := os.Stat(*unixSocket) 243 | if err == nil || !os.IsNotExist(err) { 244 | if *force { 245 | // If the socket file already exists then unlink it 246 | err = syscall.Unlink(*unixSocket) 247 | if err != nil { 248 | log.Fatalf("Failed to unlink socket %s, error '%s'\n", *unixSocket, err) 249 | } 250 | } else { 251 | log.Fatalf("Error: the SSH_AUTH_SOCK file already exists. Please delete it manually or use --force option.") 252 | } 253 | } 254 | 255 | unix, err = net.Listen("unix", *unixSocket) 256 | if err != nil { 257 | log.Fatalf("Could not open socket %s, error '%s'\n", *unixSocket, err) 258 | } 259 | 260 | defer unix.Close() 261 | log.Printf("Listening on Unix socket: %s\n", *unixSocket) 262 | go func() { 263 | listenLoop(unix) 264 | 265 | // If for some reason our listener breaks, kill the program 266 | done <- true 267 | }() 268 | } 269 | 270 | if *namedPipe != "" { 271 | namedPipeFullName := "\\\\.\\pipe\\" + *namedPipe 272 | var cfg = &winio.PipeConfig{} 273 | pipe, err = winio.ListenPipe(namedPipeFullName, cfg) 274 | if err != nil { 275 | log.Fatalf("Could not open named pipe %s, error '%s'\n", namedPipeFullName, err) 276 | } 277 | 278 | defer pipe.Close() 279 | log.Printf("Listening on named pipe: %s\n", namedPipeFullName) 280 | go func() { 281 | listenLoop(pipe) 282 | 283 | // If for some reason our listener breaks, kill the program 284 | done <- true 285 | }() 286 | } 287 | 288 | if *namedPipe == "" && *unixSocket == "" { 289 | flag.PrintDefaults() 290 | os.Exit(1) 291 | } 292 | 293 | if *systrayFlag { 294 | go func() { 295 | // Wait until we are signalled as finished 296 | <-done 297 | 298 | // If for some reason our listener breaks, kill the program 299 | systray.Quit() 300 | }() 301 | 302 | systray.Run(onSystrayReady, nil) 303 | 304 | log.Print("Exiting...") 305 | } else { 306 | // Wait until we are signalled as finished 307 | <-done 308 | 309 | log.Print("Exiting...") 310 | } 311 | } 312 | 313 | func onSystrayReady() { 314 | systray.SetTitle("WSL-SSH-Pageant") 315 | systray.SetTooltip("WSL-SSH-Pageant") 316 | 317 | data, err := Asset("assets/icon.ico") 318 | if err == nil { 319 | systray.SetIcon(data) 320 | } 321 | 322 | quit := systray.AddMenuItem("Quit", "Quits this app") 323 | 324 | go func() { 325 | for { 326 | select { 327 | case <-quit.ClickedCh: 328 | systray.Quit() 329 | return 330 | } 331 | } 332 | }() 333 | } 334 | --------------------------------------------------------------------------------