├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md └── workflows │ └── go.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── go.mod ├── go.sum └── main.go /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | on: [push] 3 | jobs: 4 | 5 | build: 6 | name: Build 7 | runs-on: ubuntu-latest 8 | steps: 9 | 10 | - name: Set up Go 1.16 11 | uses: actions/setup-go@v1 12 | with: 13 | go-version: 1.16 14 | id: go 15 | 16 | - name: Check out code into the Go module directory 17 | uses: actions/checkout@v1 18 | 19 | - name: Build 20 | run: make build 21 | 22 | - name: Upload artifact 23 | uses: actions/upload-artifact@v1.0.0 24 | with: 25 | # Artifact name 26 | name: wsl2-ssh-pageant.exe 27 | # Directory containing files to upload 28 | path: wsl2-ssh-pageant.exe 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Marc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | build: 3 | GOOS=windows go build -o wsl2-ssh-pageant.exe -ldflags -H=windowsgui main.go 4 | 5 | install: build 6 | mv wsl2-ssh-pageant.exe ~/.ssh/ 7 | 8 | listen: build 9 | socat UNIX-LISTEN:ssh.sock,fork EXEC:./wsl2-ssh-pageant.exe 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | :warning: I no longer own a Windows PC, so this project has been needless for me. 2 | For this reason i will archive this project. :warning: 3 | 4 | # wsl2-ssh-pageant 5 | 6 | ## Motivation 7 | 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 WSL2. 8 | 9 | ## How to use with WSL2 10 | 11 | ### Prerequisite 12 | In order to use `wsl-ssh-pageant` you must have installed `socat` and `ss` on your machine. 13 | 14 | For example, on Ubuntu you can install these by running: `sudo apt install socat iproute2` 15 | 16 | ### Installation 17 | 1. Download latest version from [release page](https://github.com/BlackReloaded/wsl2-ssh-pageant/releases/latest) and copy `wsl2-ssh-pageant.exe` to your windows home directory (or other location within the windows file system). Then simlink to your `$HOME/.ssh` directory for easy access 18 | ```bash 19 | windows_destination="/mnt/c/Users/Public/Downloads/wsl2-ssh-pageant.exe" 20 | linux_destination="$HOME/.ssh/wsl2-ssh-pageant.exe" 21 | wget -O "$windows_destination" "https://github.com/BlackReloaded/wsl2-ssh-pageant/releases/latest/download/wsl2-ssh-pageant.exe" 22 | # Set the executable bit. 23 | chmod +x "$windows_destination" 24 | # Symlink to linux for ease of use later 25 | ln -s $windows_destination $linux_destination 26 | ``` 27 | 2. Add one of the following to your shell configuration (for e.g. `.bashrc`, `.zshrc` or `config.fish`). For advanced configurations consult the documentation of your shell. 28 | 29 | #### Bash/Zsh 30 | 31 | *SSH:* 32 | ```bash 33 | export SSH_AUTH_SOCK="$HOME/.ssh/agent.sock" 34 | if ! ss -a | grep -q "$SSH_AUTH_SOCK"; then 35 | rm -f "$SSH_AUTH_SOCK" 36 | wsl2_ssh_pageant_bin="$HOME/.ssh/wsl2-ssh-pageant.exe" 37 | if test -x "$wsl2_ssh_pageant_bin"; then 38 | (setsid nohup socat UNIX-LISTEN:"$SSH_AUTH_SOCK,fork" EXEC:"$wsl2_ssh_pageant_bin" >/dev/null 2>&1 &) 39 | else 40 | echo >&2 "WARNING: $wsl2_ssh_pageant_bin is not executable." 41 | fi 42 | unset wsl2_ssh_pageant_bin 43 | fi 44 | ``` 45 | 46 | *GPG:* 47 | ```bash 48 | export GPG_AGENT_SOCK="$HOME/.gnupg/S.gpg-agent" 49 | if ! ss -a | grep -q "$GPG_AGENT_SOCK"; then 50 | rm -rf "$GPG_AGENT_SOCK" 51 | wsl2_ssh_pageant_bin="$HOME/.ssh/wsl2-ssh-pageant.exe" 52 | if test -x "$wsl2_ssh_pageant_bin"; then 53 | (setsid nohup socat UNIX-LISTEN:"$GPG_AGENT_SOCK,fork" EXEC:"$wsl2_ssh_pageant_bin --gpg S.gpg-agent" >/dev/null 2>&1 &) 54 | else 55 | echo >&2 "WARNING: $wsl2_ssh_pageant_bin is not executable." 56 | fi 57 | unset wsl2_ssh_pageant_bin 58 | fi 59 | ``` 60 | 61 | #### Fish 62 | 63 | *SSH:* 64 | ```fish 65 | set -x SSH_AUTH_SOCK "$HOME/.ssh/agent.sock" 66 | if not ss -a | grep -q "$SSH_AUTH_SOCK"; 67 | rm -f "$SSH_AUTH_SOCK" 68 | set wsl2_ssh_pageant_bin "$HOME/.ssh/wsl2-ssh-pageant.exe" 69 | if test -x "$wsl2_ssh_pageant_bin"; 70 | setsid nohup socat UNIX-LISTEN:"$SSH_AUTH_SOCK,fork" EXEC:"$wsl2_ssh_pageant_bin" >/dev/null 2>&1 & 71 | else 72 | echo >&2 "WARNING: $wsl2_ssh_pageant_bin is not executable." 73 | end 74 | set --erase wsl2_ssh_pageant_bin 75 | end 76 | ``` 77 | 78 | *GPG:* 79 | ```fish 80 | set -x GPG_AGENT_SOCK "$HOME/.gnupg/S.gpg-agent" 81 | if not ss -a | grep -q "$GPG_AGENT_SOCK"; 82 | rm -rf "$GPG_AGENT_SOCK" 83 | set wsl2_ssh_pageant_bin "$HOME/.ssh/wsl2-ssh-pageant.exe" 84 | if test -x "$wsl2_ssh_pageant_bin"; 85 | setsid nohup socat UNIX-LISTEN:"$GPG_AGENT_SOCK,fork" EXEC:"$wsl2_ssh_pageant_bin --gpg S.gpg-agent" >/dev/null 2>&1 & 86 | else 87 | echo >&2 "WARNING: $wsl2_ssh_pageant_bin is not executable." 88 | end 89 | set --erase wsl2_ssh_pageant_bin 90 | end 91 | ``` 92 | 93 | ## Troubleshooting 94 | 95 | ### Smartcard is detected in Windows and WSL, but ssh-add -L returns error 96 | If this is the first time you using yubikey with windows with gpg4win, please follow the instructions in the link 97 | https://developers.yubico.com/PGP/SSH_authentication/Windows.html 98 | 99 | | Make sure ssh support is enabled in the `gpg-agent.conf` and restart `gpg-agent` with the following command 100 | 101 | ``` 102 | gpg-connect-agent killagent /bye 103 | gpg-connect-agent /bye 104 | ``` 105 | 106 | ### Agent response times are very slow 107 | If ssh,ssh-add,gpg etc are very slow (~15-25 seconds) check that wsl2-ssh-pageant resides on the windows file system. This is due to an issue with the WSL interop documented [here](https://github.com/BlackReloaded/wsl2-ssh-pageant/issues/24) and [here](https://github.com/microsoft/WSL/issues/7591) 108 | 109 | ## Credit 110 | 111 | Some of the code is copied from benpye's [wsl-ssh-pageant](https://github.com/benpye/wsl-ssh-pageant). This code shows how to communicate to pageant. 112 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/blackreloaded/wsl2-ssh-pageant 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/Microsoft/go-winio v0.4.14 7 | github.com/apenwarr/fixconsole v0.0.0-20191012055117-5a9f6489cc29 8 | github.com/getlantern/systray v0.0.0-20191219131235-d922a91190d8 9 | github.com/lxn/win v0.0.0-20191128105842-2da648fda5b4 10 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8 11 | ) 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU= 2 | github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= 3 | github.com/apenwarr/fixconsole v0.0.0-20191012055117-5a9f6489cc29 h1:muXWUcay7DDy1/hEQWrYlBy+g0EuwT70sBHg65SeUc4= 4 | github.com/apenwarr/fixconsole v0.0.0-20191012055117-5a9f6489cc29/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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/davecgh/go-spew v1.1.1/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-20190830074920-4ef2e798c2d7 h1:guBYzEaLz0Vfc/jv0czrr2z7qyzTOGC9hiQ0VC+hKjk= 14 | github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7/go.mod h1:zx/1xUUeYPy3Pcmet8OSXLbF47l+3y6hIPpyLWoR9oc= 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-20191219131235-d922a91190d8 h1:oYdQDXyjSwVRs1qeBPHUKfDQ4goCMIK+wcjpWK4Ubyo= 22 | github.com/getlantern/systray v0.0.0-20191219131235-d922a91190d8/go.mod h1:4yRvwNSBNJtSOi3dRLNxhG40wXg9nEwdDjX2IrGZJo8= 23 | github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= 24 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 25 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 26 | github.com/lxn/walk v0.0.0-20191113135339-bf589de20b3c h1:4pJw1uBKndwiBBJpcbqP1Bf90YfqTJUsHG9DVoAfIQ8= 27 | github.com/lxn/walk v0.0.0-20191113135339-bf589de20b3c/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ= 28 | github.com/lxn/win v0.0.0-20191106123917-121afc750dd3/go.mod h1:ouWl4wViUNh8tPSIwxTVMuS014WakR1hqvBc2I0bMoA= 29 | github.com/lxn/win v0.0.0-20191128105842-2da648fda5b4 h1:5BmtGkQbch91lglMHQ9JIDGiYCL3kBRBA0ItZTvOcEI= 30 | github.com/lxn/win v0.0.0-20191128105842-2da648fda5b4/go.mod h1:ouWl4wViUNh8tPSIwxTVMuS014WakR1hqvBc2I0bMoA= 31 | github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw= 32 | github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= 33 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 34 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 35 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 36 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 37 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 38 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 39 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 40 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 41 | golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 42 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 43 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 44 | golang.org/x/sys v0.0.0-20190919044723-0c1ff786ef13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 45 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8 h1:JA8d3MPx/IToSyXZG/RhwYEtfrKO1Fxrqe8KrkiLXKM= 46 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 47 | gopkg.in/Knetic/govaluate.v3 v3.0.0 h1:18mUyIt4ZlRlFZAAfVetz4/rzlJs9yhN+U02F4u1AOc= 48 | gopkg.in/Knetic/govaluate.v3 v3.0.0/go.mod h1:csKLBORsPbafmSCGTEh3U7Ozmsuq8ZSIlKk1bcqph0E= 49 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "encoding/binary" 6 | "errors" 7 | "flag" 8 | "fmt" 9 | "io" 10 | "log" 11 | "net" 12 | "os" 13 | "os/exec" 14 | "path/filepath" 15 | "reflect" 16 | "strconv" 17 | "syscall" 18 | "unsafe" 19 | 20 | "github.com/Microsoft/go-winio" 21 | "github.com/apenwarr/fixconsole" 22 | "github.com/lxn/win" 23 | "golang.org/x/sys/windows" 24 | ) 25 | 26 | const ( 27 | // Windows constats 28 | invalidHandleValue = ^windows.Handle(0) 29 | pageReadWrite = 0x4 30 | fileMapWrite = 0x2 31 | 32 | // ssh-agent/Pageant constants 33 | agentMaxMessageLength = 8192 34 | agentCopyDataID = 0x804e50ba 35 | ) 36 | 37 | var ( 38 | verbose = flag.Bool("verbose", false, "Enable verbose logging") 39 | logFile = flag.String("logfile", "wsl2-gpg-ssh.log", "Path to logfile") 40 | gpg = flag.String("gpg", "", "gpg mode") 41 | gpgConfigBasepath = flag.String("gpgConfigBasepath", "", "gpg config path on windows") 42 | ssh = flag.String("ssh", "", "windows ssh mode") 43 | 44 | failureMessage = [...]byte{0, 0, 0, 1, 5} 45 | ) 46 | 47 | // copyDataStruct is used to pass data in the WM_COPYDATA message. 48 | // We directly pass a pointer to our copyDataStruct type, we need to be 49 | // careful that it matches the Windows type exactly 50 | type copyDataStruct struct { 51 | dwData uintptr 52 | cbData uint32 53 | lpData uintptr 54 | } 55 | 56 | func queryPageant(buf []byte) (result []byte, err error) { 57 | if len(buf) > agentMaxMessageLength { 58 | err = errors.New("Message too long") 59 | return 60 | } 61 | 62 | hwnd := win.FindWindow(syscall.StringToUTF16Ptr("Pageant"), syscall.StringToUTF16Ptr("Pageant")) 63 | 64 | // Launch gpg-connect-agent 65 | if hwnd == 0 { 66 | log.Println("launching gpg-connect-agent") 67 | exec.Command("gpg-connect-agent", "/bye").Run() 68 | } 69 | 70 | hwnd = win.FindWindow(syscall.StringToUTF16Ptr("Pageant"), syscall.StringToUTF16Ptr("Pageant")) 71 | if hwnd == 0 { 72 | err = errors.New("Could not find Pageant window") 73 | return 74 | } 75 | 76 | // Adding process id in order to support parrallel requests. 77 | requestName := "WSLPageantRequest" + strconv.Itoa(os.Getpid()) 78 | mapName := fmt.Sprintf(requestName) 79 | 80 | fileMap, err := windows.CreateFileMapping(invalidHandleValue, nil, pageReadWrite, 0, agentMaxMessageLength, syscall.StringToUTF16Ptr(mapName)) 81 | if err != nil { 82 | return 83 | } 84 | defer func() { 85 | windows.CloseHandle(fileMap) 86 | }() 87 | 88 | sharedMemory, err := windows.MapViewOfFile(fileMap, fileMapWrite, 0, 0, 0) 89 | if err != nil { 90 | return 91 | } 92 | defer windows.UnmapViewOfFile(sharedMemory) 93 | 94 | sharedMemoryArray := (*[agentMaxMessageLength]byte)(unsafe.Pointer(sharedMemory)) 95 | copy(sharedMemoryArray[:], buf) 96 | 97 | mapNameWithNul := mapName + "\000" 98 | 99 | // We use our knowledge of Go strings to get the length and pointer to the 100 | // data and the length directly 101 | cds := copyDataStruct{ 102 | dwData: agentCopyDataID, 103 | cbData: uint32(((*reflect.StringHeader)(unsafe.Pointer(&mapNameWithNul))).Len), 104 | lpData: ((*reflect.StringHeader)(unsafe.Pointer(&mapNameWithNul))).Data, 105 | } 106 | 107 | ret := win.SendMessage(hwnd, win.WM_COPYDATA, 0, uintptr(unsafe.Pointer(&cds))) 108 | if ret == 0 { 109 | err = errors.New("WM_COPYDATA failed") 110 | return 111 | } 112 | 113 | len := binary.BigEndian.Uint32(sharedMemoryArray[:4]) 114 | len += 4 115 | 116 | if len > agentMaxMessageLength { 117 | err = errors.New("Return message too long") 118 | return 119 | } 120 | 121 | result = make([]byte, len) 122 | copy(result, sharedMemoryArray[:len]) 123 | 124 | return 125 | } 126 | 127 | func main() { 128 | fixconsole.FixConsoleIfNeeded() 129 | flag.Parse() 130 | 131 | if *verbose { 132 | //Setting logput to file because we use stdout for communication 133 | f, err := os.OpenFile(*logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) 134 | if err != nil { 135 | log.Fatalf("error opening file: %v", err) 136 | } 137 | defer f.Close() 138 | 139 | log.SetOutput(f) 140 | log.Println("Starting exe") 141 | } 142 | 143 | if *gpg != "" { 144 | homeDir, err := os.UserHomeDir() 145 | if err != nil { 146 | log.Fatal("failed to find user home dir") 147 | } 148 | basePath := *gpgConfigBasepath 149 | // fallback to default location if not specified 150 | if basePath == "" { 151 | basePath = filepath.Join(homeDir, "AppData", "Roaming", "gnupg") 152 | } 153 | handleGPG(filepath.Join(basePath, *gpg)) 154 | return 155 | } 156 | if *ssh != "" { 157 | handlePipedSSH() 158 | return 159 | } 160 | handleSSH() 161 | } 162 | 163 | func handleGPG(path string) { 164 | var port int 165 | var nonce [16]byte 166 | 167 | file, err := os.Open(path) 168 | if err != nil { 169 | log.Fatal(err) 170 | } 171 | 172 | reader := bufio.NewReader(file) 173 | tmp, _, err := reader.ReadLine() 174 | port, err = strconv.Atoi(string(tmp)) 175 | n, err := reader.Read(nonce[:]) 176 | if err != nil { 177 | if *verbose { 178 | log.Printf("Could not read port from gpg nonce: %v\n", err) 179 | } 180 | return 181 | } 182 | 183 | if n != 16 { 184 | if *verbose { 185 | log.Printf("Could not connet gpg: incorrect number of bytes for nonceRead incorrect number of bytes for nonce\n") 186 | } 187 | return 188 | } 189 | 190 | gpgConn, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", port)) 191 | if err != nil { 192 | if *verbose { 193 | log.Printf("Could not connet gpg: %v\n", err) 194 | } 195 | return 196 | } 197 | 198 | _, err = gpgConn.Write(nonce[:]) 199 | if err != nil { 200 | if *verbose { 201 | log.Printf("Could not authenticate gpg: %v\n", err) 202 | } 203 | return 204 | } 205 | 206 | go func() { 207 | _, err := io.Copy(gpgConn, os.Stdin) 208 | if err != nil { 209 | if *verbose { 210 | log.Printf("Could not copy gpg data from assuan socket to socket: %v\n", err) 211 | } 212 | return 213 | } 214 | }() 215 | 216 | _, err = io.Copy(os.Stdout, gpgConn) 217 | if err != nil { 218 | if *verbose { 219 | log.Printf("Could not copy gpg data from socket to assuan socket: %v\n", err) 220 | } 221 | return 222 | } 223 | } 224 | 225 | func handlePipedSSH() { 226 | conn, err := winio.DialPipe(*ssh, nil) 227 | if err != nil { 228 | log.Printf("failed to dial ssh pipe at %s: %s\n", *ssh, err) 229 | return 230 | } 231 | go func() { 232 | _, err := io.Copy(os.Stdout, conn) 233 | if err != nil && err != io.EOF { 234 | log.Printf("failed to copy from pipe to stdout: %s\n", err) 235 | os.Exit(1) 236 | } 237 | }() 238 | _, err = io.Copy(conn, os.Stdin) 239 | if err != nil && err != io.EOF { 240 | log.Printf("failed to copy from stdin to pipe: %s\n", err) 241 | os.Exit(1) 242 | } 243 | } 244 | 245 | func handleSSH() { 246 | reader := bufio.NewReader(os.Stdin) 247 | for { 248 | lenBuf := make([]byte, 4) 249 | _, err := io.ReadFull(reader, lenBuf) 250 | if err != nil { 251 | if *verbose { 252 | log.Printf("io.ReadFull length error '%s'", err) 253 | } 254 | return 255 | } 256 | 257 | len := binary.BigEndian.Uint32(lenBuf) 258 | log.Printf("Reading length: %v", len) 259 | buf := make([]byte, len) 260 | _, err = io.ReadFull(reader, buf) 261 | if err != nil { 262 | if *verbose { 263 | log.Printf("io.ReadFull data error '%s'", err) 264 | } 265 | return 266 | } 267 | 268 | log.Printf("Querying pageant") 269 | result, err := queryPageant(append(lenBuf, buf...)) 270 | if err != nil { 271 | // If for some reason talking to Pageant fails we fall back to 272 | // sending an agent error to the client 273 | if *verbose { 274 | log.Printf("Pageant query error '%s'", err) 275 | } 276 | result = failureMessage[:] 277 | } 278 | 279 | _, err = os.Stdout.Write(result) 280 | if err != nil { 281 | if *verbose { 282 | log.Printf("net.Conn.Write error '%s'", err) 283 | } 284 | return 285 | } 286 | } 287 | } 288 | --------------------------------------------------------------------------------