main
1//
2// Copyright (c) 2014 David Mzareulyan
3//
4// Permission is hereby granted, free of charge, to any person obtaining a copy of this software
5// and associated documentation files (the "Software"), to deal in the Software without restriction,
6// including without limitation the rights to use, copy, modify, merge, publish, distribute,
7// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
8// is furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all copies or substantial
11// portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
14// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
16// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
17// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
18//
19
20//go:build windows
21// +build windows
22
23package sshagent
24
25// see https://github.com/Yasushi/putty/blob/master/windows/winpgntc.c#L155
26// see https://github.com/paramiko/paramiko/blob/master/paramiko/win_pageant.py
27
28import (
29 "encoding/binary"
30 "errors"
31 "fmt"
32 "sync"
33 "syscall"
34 "unsafe"
35
36 "golang.org/x/sys/windows"
37)
38
39// Maximum size of message can be sent to pageant
40const MaxMessageLen = 8192
41
42var (
43 ErrPageantNotFound = errors.New("pageant process not found")
44 ErrSendMessage = errors.New("error sending message")
45
46 ErrMessageTooLong = errors.New("message too long")
47 ErrInvalidMessageFormat = errors.New("invalid message format")
48 ErrResponseTooLong = errors.New("response too long")
49)
50
51const (
52 agentCopydataID = 0x804e50ba
53 wmCopydata = 74
54)
55
56type copyData struct {
57 dwData uintptr
58 cbData uint32
59 lpData unsafe.Pointer
60}
61
62var (
63 lock sync.Mutex
64
65 user32dll = windows.NewLazySystemDLL("user32.dll")
66 winFindWindow = winAPI(user32dll, "FindWindowW")
67 winSendMessage = winAPI(user32dll, "SendMessageW")
68
69 kernel32dll = windows.NewLazySystemDLL("kernel32.dll")
70 winGetCurrentThreadID = winAPI(kernel32dll, "GetCurrentThreadId")
71)
72
73func winAPI(dll *windows.LazyDLL, funcName string) func(...uintptr) (uintptr, uintptr, error) {
74 proc := dll.NewProc(funcName)
75 return func(a ...uintptr) (uintptr, uintptr, error) { return proc.Call(a...) }
76}
77
78// Query sends message msg to Pageant and returns response or error.
79// 'msg' is raw agent request with length prefix
80// Response is raw agent response with length prefix
81func query(msg []byte) ([]byte, error) {
82 if len(msg) > MaxMessageLen {
83 return nil, ErrMessageTooLong
84 }
85
86 msgLen := binary.BigEndian.Uint32(msg[:4])
87 if len(msg) != int(msgLen)+4 {
88 return nil, ErrInvalidMessageFormat
89 }
90
91 lock.Lock()
92 defer lock.Unlock()
93
94 paWin := pageantWindow()
95
96 if paWin == 0 {
97 return nil, ErrPageantNotFound
98 }
99
100 thID, _, _ := winGetCurrentThreadID()
101 mapName := fmt.Sprintf("PageantRequest%08x", thID)
102 pMapName, _ := syscall.UTF16PtrFromString(mapName)
103
104 mmap, err := syscall.CreateFileMapping(syscall.InvalidHandle, nil, syscall.PAGE_READWRITE, 0, MaxMessageLen+4, pMapName)
105 if err != nil {
106 return nil, err
107 }
108 defer syscall.CloseHandle(mmap)
109
110 ptr, err := syscall.MapViewOfFile(mmap, syscall.FILE_MAP_WRITE, 0, 0, 0)
111 if err != nil {
112 return nil, err
113 }
114 defer syscall.UnmapViewOfFile(ptr)
115
116 mmSlice := (*(*[MaxMessageLen]byte)(unsafe.Pointer(ptr)))[:]
117
118 copy(mmSlice, msg)
119
120 mapNameBytesZ := append([]byte(mapName), 0)
121
122 cds := copyData{
123 dwData: agentCopydataID,
124 cbData: uint32(len(mapNameBytesZ)),
125 lpData: unsafe.Pointer(&(mapNameBytesZ[0])),
126 }
127
128 resp, _, _ := winSendMessage(paWin, wmCopydata, 0, uintptr(unsafe.Pointer(&cds)))
129
130 if resp == 0 {
131 return nil, ErrSendMessage
132 }
133
134 respLen := binary.BigEndian.Uint32(mmSlice[:4])
135 if respLen > MaxMessageLen-4 {
136 return nil, ErrResponseTooLong
137 }
138
139 respData := make([]byte, respLen+4)
140 copy(respData, mmSlice)
141
142 return respData, nil
143}
144
145func pageantWindow() uintptr {
146 nameP, _ := syscall.UTF16PtrFromString("Pageant")
147 h, _, _ := winFindWindow(uintptr(unsafe.Pointer(nameP)), uintptr(unsafe.Pointer(nameP)))
148 return h
149}