main
Raw Download raw file
  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}